@anysiteio/agent-skills 1.2.0 → 1.4.0
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.
- package/.claude-plugin/marketplace.json +13 -1
- package/bin/install.js +174 -45
- package/package.json +1 -1
- package/skills/anysite-cli/SKILL.md +434 -178
- package/skills/anysite-cli/references/api-reference.md +73 -8
- package/skills/anysite-cli/references/dataset-guide.md +455 -19
- package/skills/anysite-cli/references/llm-reference.md +274 -0
- package/skills/anysite-mcp-migration/SKILL.md +279 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Dataset Pipeline & Database Guide
|
|
2
2
|
|
|
3
|
+
## Agent Workflow
|
|
4
|
+
|
|
5
|
+
Follow these steps when building a new dataset pipeline:
|
|
6
|
+
|
|
7
|
+
1. **Discover endpoint fields**: `anysite describe <endpoint> --json` — see input params (with examples, defaults) and output fields (with nested structure)
|
|
8
|
+
2. **Draft YAML config** — use output fields from step 1 for `dependency.field` paths
|
|
9
|
+
3. **Validate**: `anysite dataset validate dataset.yaml --json` — catches structural errors in < 0.1s
|
|
10
|
+
4. **Dry run**: `anysite dataset collect dataset.yaml --dry-run` — preview plan with estimated request counts
|
|
11
|
+
5. **Collect**: `anysite dataset collect dataset.yaml` or `anysite dataset collect dataset.yaml --load-db mydb`
|
|
12
|
+
|
|
13
|
+
**Key rule**: ALWAYS run `anysite describe` before setting `dependency.field`. The output fields show exactly which paths exist. For example, `/api/linkedin/user/posts` output has `author` as `object{@type,internal_id,urn,...}` — so the author's URN is `author.urn.value`, NOT `urn.value` (which is the post's own URN).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
3
17
|
## Dataset YAML Configuration
|
|
4
18
|
|
|
5
19
|
### Full Structure
|
|
@@ -11,7 +25,7 @@ description: Optional description
|
|
|
11
25
|
sources:
|
|
12
26
|
- id: source_id # Unique identifier
|
|
13
27
|
endpoint: /api/... # API endpoint path
|
|
14
|
-
|
|
28
|
+
input: {} # Static API parameters
|
|
15
29
|
from_file: inputs.txt # Input file (txt/JSONL/CSV)
|
|
16
30
|
file_field: column_name # CSV column to extract
|
|
17
31
|
input_key: param_name # API parameter for input values
|
|
@@ -24,8 +38,10 @@ sources:
|
|
|
24
38
|
parallel: 3 # Concurrent requests
|
|
25
39
|
rate_limit: "10/s" # Rate limiting
|
|
26
40
|
on_error: skip # stop or skip
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
refresh: always # auto (default), always, or never (skip if data exists)
|
|
42
|
+
filter: '.count > 10' # Source filter (before LLM + Parquet write)
|
|
43
|
+
transform: # Export filter (after Parquet write, for exports only)
|
|
44
|
+
filter: '.status == "active"'
|
|
29
45
|
fields: [name, url] # Field selection with aliases
|
|
30
46
|
add_columns: # Static columns to inject
|
|
31
47
|
batch: "q1-2026"
|
|
@@ -36,9 +52,28 @@ sources:
|
|
|
36
52
|
- type: webhook
|
|
37
53
|
url: https://example.com/hook
|
|
38
54
|
headers: {X-Token: abc}
|
|
55
|
+
llm: # LLM enrichment (after collection, before Parquet)
|
|
56
|
+
- type: enrich # enrich, classify, summarize, generate
|
|
57
|
+
add: # Field specs for enrich type
|
|
58
|
+
- "sentiment:positive/negative/neutral"
|
|
59
|
+
- "language:string"
|
|
60
|
+
fields: [name, headline] # Record fields to include in LLM prompt
|
|
61
|
+
- type: classify
|
|
62
|
+
categories: "dev,recruiter,exec" # Omit for auto-detect
|
|
63
|
+
output_column: role # Default: "category"
|
|
64
|
+
- type: summarize
|
|
65
|
+
max_length: 50
|
|
66
|
+
output_column: bio # Default: "summary"
|
|
67
|
+
- type: generate
|
|
68
|
+
prompt: "Pitch for {name}" # Required for generate
|
|
69
|
+
output_column: pitch # Default: "text"
|
|
70
|
+
temperature: 0.7 # Per-step overrides
|
|
39
71
|
db_load: # Database loading config
|
|
72
|
+
filter: '.active == true' # DB load filter (only matching records to DB)
|
|
40
73
|
table: custom_name # Override table name
|
|
41
|
-
|
|
74
|
+
key: urn.value # Unique key for diff-based incremental sync
|
|
75
|
+
sync: full # full (INSERT/DELETE/UPDATE) or append (no DELETE)
|
|
76
|
+
fields: [name, url, sentiment, language, role, bio, pitch]
|
|
42
77
|
exclude: [_input_value] # Fields to exclude
|
|
43
78
|
|
|
44
79
|
storage:
|
|
@@ -57,13 +92,13 @@ notifications: # Webhook notifications
|
|
|
57
92
|
- url: "https://alerts.example.com/fail"
|
|
58
93
|
```
|
|
59
94
|
|
|
60
|
-
###
|
|
95
|
+
### Six Source Types
|
|
61
96
|
|
|
62
|
-
**Independent** — single API call with static
|
|
97
|
+
**Independent** — single API call with static input:
|
|
63
98
|
```yaml
|
|
64
99
|
- id: search_results
|
|
65
100
|
endpoint: /api/linkedin/search/users
|
|
66
|
-
|
|
101
|
+
input:
|
|
67
102
|
keywords: "software engineer"
|
|
68
103
|
count: 50
|
|
69
104
|
```
|
|
@@ -93,6 +128,61 @@ notifications: # Webhook notifications
|
|
|
93
128
|
count: 5
|
|
94
129
|
```
|
|
95
130
|
|
|
131
|
+
**Union** — combine multiple sources into one:
|
|
132
|
+
```yaml
|
|
133
|
+
- id: all_results
|
|
134
|
+
type: union
|
|
135
|
+
sources: [search_cto, search_vp] # Parent source IDs to combine
|
|
136
|
+
dedupe_by: urn.value # Optional: remove duplicates by field
|
|
137
|
+
```
|
|
138
|
+
All parent sources must have the same endpoint. Records are annotated with `_union_source` (parent source ID). Cannot have `endpoint`, `dependency`, `from_file`, `input_key`, or `input`.
|
|
139
|
+
|
|
140
|
+
**LLM Source (type: llm)** — process parent data through LLM without API calls:
|
|
141
|
+
```yaml
|
|
142
|
+
- id: profiles_analyzed
|
|
143
|
+
type: llm # Source type: api (default) | llm
|
|
144
|
+
dependency:
|
|
145
|
+
from_source: profiles # Parent source (required)
|
|
146
|
+
field: name # Optional — omit if not extracting values
|
|
147
|
+
llm: # LLM enrichment steps (required for type: llm)
|
|
148
|
+
- type: classify
|
|
149
|
+
categories: "strong_fit,moderate_fit,weak_fit"
|
|
150
|
+
output_column: fit_category
|
|
151
|
+
fields: [headline, experience]
|
|
152
|
+
- type: enrich
|
|
153
|
+
add:
|
|
154
|
+
- "fit_score:1-10"
|
|
155
|
+
- "key_strengths:string"
|
|
156
|
+
- "concerns:string"
|
|
157
|
+
fields: [name, headline, experience, skills]
|
|
158
|
+
export:
|
|
159
|
+
- type: file
|
|
160
|
+
path: ./output/analyzed-{{date}}.csv
|
|
161
|
+
format: csv
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**type: llm source rules:**
|
|
165
|
+
- **Requires**: `dependency` (parent source) and non-empty `llm` list
|
|
166
|
+
- **Cannot have**: `endpoint`, `from_file`, `input_key`, `input`
|
|
167
|
+
- **Use case**: Run LLM enrichment on already-collected data without re-calling the API
|
|
168
|
+
- **Run with**: `anysite dataset collect dataset.yaml --source profiles_analyzed`
|
|
169
|
+
|
|
170
|
+
The LLM source reads the parent's Parquet data directly, applies LLM steps, and writes enriched records to its own Parquet file. This allows re-analyzing data with different prompts or categories without re-collecting from the API.
|
|
171
|
+
|
|
172
|
+
**SQL (type: sql)** — query a named database connection:
|
|
173
|
+
```yaml
|
|
174
|
+
- id: billing_users
|
|
175
|
+
type: sql
|
|
176
|
+
connection: billing
|
|
177
|
+
query: "SELECT name, email FROM subscriptions WHERE status = 'inactive'"
|
|
178
|
+
```
|
|
179
|
+
Runs the query and stores results as records. Downstream sources can depend on SQL output. Uses connections from `anysite db add`. Supports `query_file` for external `.sql` files.
|
|
180
|
+
|
|
181
|
+
**type: sql source rules:**
|
|
182
|
+
- **Requires**: `connection` and either `query` or `query_file`
|
|
183
|
+
- **Cannot have**: `endpoint`, `from_file`, `input_key`, `input`, `parallel`, `rate_limit`, `on_error`
|
|
184
|
+
- **Use case**: Bring external DB data into the pipeline for API enrichment
|
|
185
|
+
|
|
96
186
|
### Dependency Chains
|
|
97
187
|
|
|
98
188
|
Sources are topologically sorted — parents always run before children. Multi-level chains work automatically:
|
|
@@ -101,6 +191,14 @@ Sources are topologically sorted — parents always run before children. Multi-l
|
|
|
101
191
|
companies → employees → profiles → posts → comments
|
|
102
192
|
```
|
|
103
193
|
|
|
194
|
+
**Common dependency fields:**
|
|
195
|
+
- `/api/linkedin/company/employees` returns: `name`, `headline`, `url`, `image`, `location`, `internal_id`, `urn` — use `urn.value` (not `alias`) to chain into `/api/linkedin/user`
|
|
196
|
+
- `/api/linkedin/user` accepts both human-readable aliases (`satyanadella`) and URN values as the `user` parameter
|
|
197
|
+
|
|
198
|
+
Always run `anysite describe <endpoint>` to verify available fields before setting up dependencies.
|
|
199
|
+
|
|
200
|
+
**Note:** You can also use the `${source.field}` shorthand instead of explicit `dependency` + `input_key`. See the "Shorthand Syntax" section below.
|
|
201
|
+
|
|
104
202
|
### input_template
|
|
105
203
|
|
|
106
204
|
Transforms extracted values before passing to the API. Use `{value}` placeholder:
|
|
@@ -124,11 +222,124 @@ When Parquet stores nested objects as JSON strings, dot-notation traverses them:
|
|
|
124
222
|
- `experience[0].company_urn` — array index + nested field
|
|
125
223
|
- `internal_id.value` — nested object access
|
|
126
224
|
|
|
225
|
+
### Common Dependency Chains
|
|
226
|
+
|
|
227
|
+
Quick reference for popular endpoint combinations. Always verify with `anysite describe <endpoint>`.
|
|
228
|
+
|
|
229
|
+
| Parent endpoint | Child endpoint | `field` | `input_key` | Notes |
|
|
230
|
+
|----------------|---------------|---------|-------------|-------|
|
|
231
|
+
| `/api/linkedin/search/users` | `/api/linkedin/user` | `urn.value` | `user` | |
|
|
232
|
+
| `/api/linkedin/user` | `/api/linkedin/user/posts` | `urn.value` | `urn` | |
|
|
233
|
+
| `/api/linkedin/user/posts` | `/api/linkedin/post/comments` | `urn.value` | `urn` | |
|
|
234
|
+
| `/api/linkedin/user/posts` | `/api/linkedin/user` (author) | `author.urn.value` | `user` | NOT `urn.value` |
|
|
235
|
+
| `/api/linkedin/user` | `/api/linkedin/company` | `company.universalName` | `company` | |
|
|
236
|
+
| `/api/linkedin/company` | `/api/linkedin/company/posts` | `universalName` | `company` | needs `input_template` |
|
|
237
|
+
| `/api/linkedin/company` | `/api/linkedin/company/employees` | `urn.value` | `companies` | needs `input_template` |
|
|
238
|
+
|
|
239
|
+
**Posts → Author Profiles** (most common mistake — using wrong field):
|
|
240
|
+
```yaml
|
|
241
|
+
- id: author_profiles
|
|
242
|
+
endpoint: /api/linkedin/user
|
|
243
|
+
dependency:
|
|
244
|
+
from_source: posts
|
|
245
|
+
field: author.urn.value # AUTHOR's URN, not post URN
|
|
246
|
+
dedupe: true
|
|
247
|
+
input_key: user
|
|
248
|
+
parallel: 5
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Companies → Employees** (requires array input_template):
|
|
252
|
+
```yaml
|
|
253
|
+
- id: employees
|
|
254
|
+
endpoint: /api/linkedin/company/employees
|
|
255
|
+
dependency:
|
|
256
|
+
from_source: companies
|
|
257
|
+
field: urn.value
|
|
258
|
+
input_key: companies
|
|
259
|
+
input_template:
|
|
260
|
+
companies:
|
|
261
|
+
- type: company
|
|
262
|
+
value: "{value}"
|
|
263
|
+
count: 5
|
|
264
|
+
parallel: 2
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Shorthand Syntax (`${source.field}`)
|
|
268
|
+
|
|
269
|
+
Instead of writing explicit `dependency` + `input_key`, you can use `${source.field}` directly in `input`:
|
|
270
|
+
|
|
271
|
+
```yaml
|
|
272
|
+
# Shorthand (auto-expands to dependency + input_key):
|
|
273
|
+
- id: profiles
|
|
274
|
+
endpoint: /api/linkedin/user
|
|
275
|
+
input:
|
|
276
|
+
user: ${search_results.urn.value}
|
|
277
|
+
count: 20
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
This is equivalent to:
|
|
281
|
+
```yaml
|
|
282
|
+
- id: profiles
|
|
283
|
+
endpoint: /api/linkedin/user
|
|
284
|
+
dependency:
|
|
285
|
+
from_source: search_results
|
|
286
|
+
field: urn.value
|
|
287
|
+
input_key: user
|
|
288
|
+
input:
|
|
289
|
+
count: 20
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
The `${source.field}` syntax extracts the source ID and field path automatically. Static parameters (like `count: 20`) are passed through as-is.
|
|
293
|
+
|
|
294
|
+
### Required Fields by Source Type
|
|
295
|
+
|
|
296
|
+
| Field | `api` (default) | `llm` | `union` | `sql` |
|
|
297
|
+
|-------|-----------------|-------|---------|-------|
|
|
298
|
+
| `id` | required | required | required | required |
|
|
299
|
+
| `type` | optional (defaults to "api") | required: `llm` | required: `union` | required: `sql` |
|
|
300
|
+
| `endpoint` | required | N/A | N/A | N/A |
|
|
301
|
+
| `connection` | N/A | N/A | N/A | **required** |
|
|
302
|
+
| `query` | N/A | N/A | N/A | required (or `query_file`) |
|
|
303
|
+
| `query_file` | N/A | N/A | N/A | required (or `query`) |
|
|
304
|
+
| `dependency` | optional | **required** | N/A | optional |
|
|
305
|
+
| `llm` | optional | **required** (>= 1 step) | optional | optional |
|
|
306
|
+
| `sources` | N/A | N/A | **required** | N/A |
|
|
307
|
+
| `input` | optional | N/A | N/A | N/A |
|
|
308
|
+
| `input_key` | optional | N/A | N/A | N/A |
|
|
309
|
+
| `input_template` | optional | N/A | N/A | N/A |
|
|
310
|
+
| `from_file` | optional | N/A | N/A | N/A |
|
|
311
|
+
| `parallel` | optional (default: 1) | N/A | N/A | N/A |
|
|
312
|
+
| `filter` | optional | optional | optional | optional |
|
|
313
|
+
| `transform` | optional | optional | optional | optional |
|
|
314
|
+
| `export` | optional | optional | optional | optional |
|
|
315
|
+
| `db_load` | optional | optional | optional | optional |
|
|
316
|
+
| `dedupe_by` | N/A | N/A | optional | N/A |
|
|
317
|
+
|
|
318
|
+
**Key notes:**
|
|
319
|
+
- `dependency.field` is optional (not required) — needed for value extraction but not for LLM sources
|
|
320
|
+
- `endpoint` must start with `/` (e.g., `/api/linkedin/user`)
|
|
321
|
+
- `categories` in classify steps is a comma-separated **string**, not an array
|
|
322
|
+
- `partition_by` in storage config is accepted but not currently used — layout is always `raw/<source_id>/<date>.parquet`
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Validation
|
|
327
|
+
|
|
328
|
+
Validate dataset config before collecting. Catches structural errors, missing dependencies, invalid filters, and bad LLM specs.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
anysite dataset validate dataset.yaml # Validate config
|
|
332
|
+
anysite dataset validate dataset.yaml --json # Machine-readable output
|
|
333
|
+
```
|
|
334
|
+
|
|
127
335
|
---
|
|
128
336
|
|
|
129
337
|
## Collection Commands
|
|
130
338
|
|
|
131
339
|
```bash
|
|
340
|
+
# Validate config first (recommended)
|
|
341
|
+
anysite dataset validate dataset.yaml
|
|
342
|
+
|
|
132
343
|
# Preview collection plan with estimated request counts
|
|
133
344
|
anysite dataset collect dataset.yaml --dry-run
|
|
134
345
|
|
|
@@ -146,6 +357,9 @@ anysite dataset collect dataset.yaml --source employees
|
|
|
146
357
|
|
|
147
358
|
# Quiet mode
|
|
148
359
|
anysite dataset collect dataset.yaml --quiet
|
|
360
|
+
|
|
361
|
+
# Pilot run: max 100 inputs per source
|
|
362
|
+
anysite dataset collect dataset.yaml --limit 100
|
|
149
363
|
```
|
|
150
364
|
|
|
151
365
|
### Provenance Tracking
|
|
@@ -163,6 +377,26 @@ With `--incremental`:
|
|
|
163
377
|
2. Dependent/from_file sources: skips individual input values already in `metadata.json`
|
|
164
378
|
3. New values are still collected and tracked
|
|
165
379
|
|
|
380
|
+
### Refresh Mode
|
|
381
|
+
|
|
382
|
+
Per-source `refresh` field controls behavior with `--incremental`:
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
- id: posts
|
|
386
|
+
endpoint: /api/linkedin/user/posts
|
|
387
|
+
dependency: { from_source: profiles, field: urn.value }
|
|
388
|
+
input_key: user
|
|
389
|
+
refresh: always # Re-collect every run even with --incremental
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
| Setting | `--incremental` | No flag |
|
|
393
|
+
|---------|----------------|---------|
|
|
394
|
+
| `refresh: auto` (default) | Skip collected inputs | Collect all |
|
|
395
|
+
| `refresh: always` | Collect all (ignore cache) | Collect all |
|
|
396
|
+
| `refresh: never` | Skip if data exists | Skip if data exists |
|
|
397
|
+
|
|
398
|
+
Use `refresh: always` for sources with frequently changing data (e.g., posts, activity feeds) where you want fresh snapshots each run while still caching stable parent data. Use `refresh: never` for stable reference data that only needs to be collected once.
|
|
399
|
+
|
|
166
400
|
### Storage Layout
|
|
167
401
|
|
|
168
402
|
```
|
|
@@ -175,7 +409,7 @@ With `--incremental`:
|
|
|
175
409
|
|
|
176
410
|
## Query Commands (DuckDB)
|
|
177
411
|
|
|
178
|
-
Each source becomes a DuckDB view named after its ID.
|
|
412
|
+
Each source becomes a DuckDB view named after its ID. Hyphens and dots in source IDs are replaced with underscores (e.g., `search-results` → `search_results`).
|
|
179
413
|
|
|
180
414
|
```bash
|
|
181
415
|
# Direct SQL
|
|
@@ -189,6 +423,9 @@ anysite dataset query dataset.yaml --source profiles \
|
|
|
189
423
|
--fields "name, urn.value AS urn_id, headline"
|
|
190
424
|
# Generates: SELECT name, json_extract_string(urn, '$.value') AS urn_id, headline FROM profiles
|
|
191
425
|
|
|
426
|
+
# Exclude internal columns
|
|
427
|
+
anysite dataset query dataset.yaml --source profiles --exclude "_input_value,_parent_source"
|
|
428
|
+
|
|
192
429
|
# Output options
|
|
193
430
|
anysite dataset query dataset.yaml --sql "SELECT * FROM companies" \
|
|
194
431
|
--format csv --output companies.csv
|
|
@@ -218,6 +455,7 @@ anysite dataset load-db dataset.yaml -c <connection_name> [OPTIONS]
|
|
|
218
455
|
--connection, -c TEXT Database connection name (required)
|
|
219
456
|
--source, -s TEXT Load specific source + dependencies
|
|
220
457
|
--drop-existing Drop tables before creating
|
|
458
|
+
--snapshot TEXT Load a specific snapshot date (YYYY-MM-DD)
|
|
221
459
|
--dry-run Show plan without executing
|
|
222
460
|
--quiet, -q Suppress output
|
|
223
461
|
```
|
|
@@ -230,6 +468,29 @@ anysite dataset load-db dataset.yaml -c <connection_name> [OPTIONS]
|
|
|
230
468
|
4. Inserts rows, tracking which `_input_value` maps to which `id`
|
|
231
469
|
5. For child sources: adds `{parent_source}_id` FK column using provenance
|
|
232
470
|
|
|
471
|
+
### Incremental Sync with `db_load.key`
|
|
472
|
+
|
|
473
|
+
When `db_load.key` is set and the table already exists with >=2 snapshots, `load-db` uses diff-based incremental sync instead of full re-insertion:
|
|
474
|
+
|
|
475
|
+
1. Compares the two most recent Parquet snapshots using `DatasetDiffer`
|
|
476
|
+
2. **Added** records → INSERT into DB
|
|
477
|
+
3. **Removed** records → DELETE from DB (by key) — only in `sync: full` mode
|
|
478
|
+
4. **Changed** records → UPDATE modified fields (by key)
|
|
479
|
+
|
|
480
|
+
This keeps the database in sync without duplicates.
|
|
481
|
+
|
|
482
|
+
**Sync modes** (`db_load.sync`):
|
|
483
|
+
- `full` (default) — applies INSERT, DELETE, and UPDATE from diff
|
|
484
|
+
- `append` — applies INSERT and UPDATE only, skips DELETE. Use for sources where the API returns only the latest N items (e.g., posts, comments) and you want to accumulate records over time.
|
|
485
|
+
|
|
486
|
+
| Scenario | Behavior |
|
|
487
|
+
|----------|----------|
|
|
488
|
+
| First load (table doesn't exist) | Full INSERT of latest snapshot |
|
|
489
|
+
| Table exists + `db_load.key` + >=2 snapshots | Diff-based sync (INSERT/DELETE/UPDATE delta) |
|
|
490
|
+
| `--drop-existing` | Drop table, full INSERT of latest snapshot |
|
|
491
|
+
| `--snapshot 2026-01-15` | Full INSERT of that specific snapshot |
|
|
492
|
+
| No `db_load.key` set | Full INSERT of latest snapshot (no diff) |
|
|
493
|
+
|
|
233
494
|
### db_load Config
|
|
234
495
|
|
|
235
496
|
Control which fields go to the database per source:
|
|
@@ -237,6 +498,8 @@ Control which fields go to the database per source:
|
|
|
237
498
|
```yaml
|
|
238
499
|
db_load:
|
|
239
500
|
table: people # Custom table name (default: source ID)
|
|
501
|
+
key: urn.value # Unique key for diff-based incremental sync
|
|
502
|
+
sync: append # full (default) or append (no DELETE on diff)
|
|
240
503
|
fields: # Explicit field list
|
|
241
504
|
- name
|
|
242
505
|
- url
|
|
@@ -265,7 +528,8 @@ Result in database:
|
|
|
265
528
|
|
|
266
529
|
### Connection Management
|
|
267
530
|
```bash
|
|
268
|
-
anysite db add <name>
|
|
531
|
+
anysite db add <name> --type postgres --host localhost --database mydb --user app --password secret
|
|
532
|
+
anysite db add <name> --type postgres --host localhost --database mydb --user app --password-env DB_PASS
|
|
269
533
|
anysite db list # List all connections
|
|
270
534
|
anysite db test <name> # Test connectivity
|
|
271
535
|
anysite db info <name> # Show connection details
|
|
@@ -309,9 +573,54 @@ anysite db query <name> --file report.sql --format csv --output report.csv
|
|
|
309
573
|
|
|
310
574
|
---
|
|
311
575
|
|
|
312
|
-
##
|
|
576
|
+
## Three-Level Filtering
|
|
313
577
|
|
|
314
|
-
|
|
578
|
+
The pipeline supports filtering at three independent stages:
|
|
579
|
+
|
|
580
|
+
```
|
|
581
|
+
Collection → [source.filter] → LLM → Parquet → [transform.filter] → Export
|
|
582
|
+
[db_load.filter] → DB
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
| Level | Config | When | Effect |
|
|
586
|
+
|-------|--------|------|--------|
|
|
587
|
+
| 1 | `source.filter` | After collection, before LLM + Parquet | Drops records entirely |
|
|
588
|
+
| 2 | `transform.filter` | After Parquet, before exports | Parquet keeps all |
|
|
589
|
+
| 3 | `db_load.filter` | Before DB loading | Parquet keeps all |
|
|
590
|
+
|
|
591
|
+
```yaml
|
|
592
|
+
- id: analyzed
|
|
593
|
+
type: llm
|
|
594
|
+
dependency: { from_source: comments, field: urn.value }
|
|
595
|
+
filter: '.is_commenter_post_author == false' # Level 1
|
|
596
|
+
llm:
|
|
597
|
+
- type: enrich
|
|
598
|
+
add: ["score:1-10"]
|
|
599
|
+
transform:
|
|
600
|
+
filter: '.score > 5' # Level 2
|
|
601
|
+
fields: [text, author, score]
|
|
602
|
+
db_load:
|
|
603
|
+
filter: '.score > 8' # Level 3
|
|
604
|
+
fields: [text, author, score]
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Filter Syntax
|
|
608
|
+
Safe expression parser (no `eval()`). Supported operators: `==`, `!=`, `>`, `<`, `>=`, `<=`. Connectors: `and`, `or`. Values: strings (`"..."`), numbers, booleans (`true`/`false`), `null`.
|
|
609
|
+
|
|
610
|
+
```
|
|
611
|
+
.field > 10
|
|
612
|
+
.status == "active"
|
|
613
|
+
.active == true
|
|
614
|
+
.hidden == false
|
|
615
|
+
.location != ""
|
|
616
|
+
.name != null
|
|
617
|
+
.count > 5 and .count < 100
|
|
618
|
+
.status == "active" or .status == "pending"
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Per-Source Transform
|
|
622
|
+
|
|
623
|
+
`transform` block applies to export destinations only. Parquet always stores full records.
|
|
315
624
|
|
|
316
625
|
```yaml
|
|
317
626
|
transform:
|
|
@@ -326,18 +635,85 @@ transform:
|
|
|
326
635
|
source: "linkedin"
|
|
327
636
|
```
|
|
328
637
|
|
|
329
|
-
|
|
330
|
-
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## LLM Enrichment
|
|
641
|
+
|
|
642
|
+
Optional `llm` list per source. Steps run **after collection, before Parquet write**, so enriched fields are stored in Parquet and flow to DB via `db_load.fields`.
|
|
643
|
+
|
|
644
|
+
### Step Types
|
|
645
|
+
|
|
646
|
+
| Type | Purpose | Required | Output |
|
|
647
|
+
|------|---------|----------|--------|
|
|
648
|
+
| `enrich` | Extract structured attributes | `add` specs | One column per spec |
|
|
649
|
+
| `classify` | Categorize records | `categories` (optional — auto-detects if omitted) | `category` + `category_confidence` |
|
|
650
|
+
| `summarize` | Concise text summary | — | `summary` |
|
|
651
|
+
| `generate` | Custom text from prompt template | `prompt` | `text` |
|
|
652
|
+
|
|
653
|
+
### Config Example
|
|
331
654
|
|
|
655
|
+
```yaml
|
|
656
|
+
sources:
|
|
657
|
+
- id: profiles
|
|
658
|
+
endpoint: /api/linkedin/user
|
|
659
|
+
llm:
|
|
660
|
+
- type: enrich
|
|
661
|
+
add:
|
|
662
|
+
- "sentiment:positive/negative/neutral"
|
|
663
|
+
- "language:string"
|
|
664
|
+
- "quality_score:1-10"
|
|
665
|
+
fields: [headline, summary] # Fields sent to LLM (empty = all)
|
|
666
|
+
- type: classify
|
|
667
|
+
categories: "developer,recruiter,executive"
|
|
668
|
+
output_column: role_type # Default: "category"
|
|
669
|
+
fields: [headline]
|
|
670
|
+
- type: summarize
|
|
671
|
+
max_length: 50
|
|
672
|
+
output_column: bio # Default: "summary"
|
|
673
|
+
- type: generate
|
|
674
|
+
prompt: "Write a pitch for {name} who works as {headline}"
|
|
675
|
+
output_column: pitch # Default: "text"
|
|
676
|
+
temperature: 0.7 # Higher for creative generation
|
|
677
|
+
db_load:
|
|
678
|
+
fields: [name, headline, sentiment, language, quality_score, role_type, bio, pitch]
|
|
332
679
|
```
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
680
|
+
|
|
681
|
+
### Enrich Field Spec Formats
|
|
682
|
+
|
|
683
|
+
| Format | Example | JSON Schema Type |
|
|
684
|
+
|--------|---------|-----------------|
|
|
685
|
+
| Enum | `sentiment:positive/negative/neutral` | `string` with `enum` |
|
|
686
|
+
| String | `language:string` | `string` |
|
|
687
|
+
| Integer | `count:integer` | `integer` |
|
|
688
|
+
| Range | `score:1-10` | `integer` |
|
|
689
|
+
| Number | `weight:number` | `number` |
|
|
690
|
+
| Boolean | `active:boolean` | `boolean` |
|
|
691
|
+
|
|
692
|
+
### Per-Step Overrides
|
|
693
|
+
|
|
694
|
+
Each step supports: `provider`, `model`, `temperature`, `max_tokens`, `fields`, `output_column`.
|
|
695
|
+
|
|
696
|
+
### Collect Flags
|
|
697
|
+
|
|
698
|
+
```bash
|
|
699
|
+
# Collect with LLM enrichment (default when llm: is configured)
|
|
700
|
+
anysite dataset collect dataset.yaml
|
|
701
|
+
|
|
702
|
+
# Skip LLM steps
|
|
703
|
+
anysite dataset collect dataset.yaml --no-llm
|
|
704
|
+
|
|
705
|
+
# Dry run shows LLM step count per source
|
|
706
|
+
anysite dataset collect dataset.yaml --dry-run
|
|
339
707
|
```
|
|
340
708
|
|
|
709
|
+
### LLM Response Caching
|
|
710
|
+
|
|
711
|
+
LLM responses are cached in SQLite (`~/.anysite/llm_cache.db`). Re-running collection on the same data reuses cached results without calling the LLM provider. Clear with `anysite llm cache-clear`.
|
|
712
|
+
|
|
713
|
+
### Incremental Behavior
|
|
714
|
+
|
|
715
|
+
`--incremental` skips already-collected inputs at collection stage — only new records reach LLM enrichment. LLM response cache provides additional savings for repeated content.
|
|
716
|
+
|
|
341
717
|
---
|
|
342
718
|
|
|
343
719
|
## Per-Source Export
|
|
@@ -421,6 +797,42 @@ Payload: `{event: "complete"|"failure", dataset, timestamp, record_count, source
|
|
|
421
797
|
|
|
422
798
|
---
|
|
423
799
|
|
|
800
|
+
## Comparing Snapshots (Diff)
|
|
801
|
+
|
|
802
|
+
Compare two collection snapshots to find added, removed, and changed records.
|
|
803
|
+
|
|
804
|
+
```bash
|
|
805
|
+
# Compare two most recent snapshots (auto-detect dates)
|
|
806
|
+
anysite dataset diff dataset.yaml --source profiles --key _input_value
|
|
807
|
+
|
|
808
|
+
# Compare with dot-notation key (JSON fields)
|
|
809
|
+
anysite dataset diff dataset.yaml --source profiles --key urn.value
|
|
810
|
+
|
|
811
|
+
# Compare specific dates
|
|
812
|
+
anysite dataset diff dataset.yaml --source profiles --key urn.value --from 2026-01-30 --to 2026-02-01
|
|
813
|
+
|
|
814
|
+
# Only compare specific fields
|
|
815
|
+
anysite dataset diff dataset.yaml --source profiles --key urn --fields "name,headline,follower_count"
|
|
816
|
+
|
|
817
|
+
# Output as JSON/CSV
|
|
818
|
+
anysite dataset diff dataset.yaml --source profiles --key urn --format json --output diff.json
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
**Options:**
|
|
822
|
+
- `--source, -s` (required) — source to compare
|
|
823
|
+
- `--key, -k` (required) — field to match records by. Supports dot-notation for JSON fields (e.g., `urn.value`)
|
|
824
|
+
- `--from` / `--to` — snapshot dates (default: two most recent)
|
|
825
|
+
- `--fields, -f` — restrict both comparison and output to these fields
|
|
826
|
+
- `--format` — output format (table, json, jsonl, csv)
|
|
827
|
+
- `--output, -o` — write to file
|
|
828
|
+
|
|
829
|
+
**Output** shows summary counts and a table of changes:
|
|
830
|
+
- **added** — records in the new snapshot but not the old
|
|
831
|
+
- **removed** — records in the old snapshot but not the new
|
|
832
|
+
- **changed** — records with the same key but different values (shows `old → new`)
|
|
833
|
+
|
|
834
|
+
---
|
|
835
|
+
|
|
424
836
|
## Reset Incremental State
|
|
425
837
|
|
|
426
838
|
Clear collected input tracking to force re-collection.
|
|
@@ -432,3 +844,27 @@ anysite dataset reset-cursor dataset.yaml
|
|
|
432
844
|
# Reset specific source
|
|
433
845
|
anysite dataset reset-cursor dataset.yaml --source profiles
|
|
434
846
|
```
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
## Common Mistakes
|
|
851
|
+
|
|
852
|
+
| # | Mistake | Fix |
|
|
853
|
+
|---|---------|-----|
|
|
854
|
+
| 1 | `field: urn.value` for author profiles (wrong URN) | Use `field: author.urn.value` — run `anysite describe` to find correct path |
|
|
855
|
+
| 2 | `input_template: { company: "microsoft" }` (missing placeholder) | Use `company: "{value}"` — `{value}` is replaced with extracted value |
|
|
856
|
+
| 3 | `endpoint: api/linkedin/user` (no leading `/`) | Use `endpoint: /api/linkedin/user` |
|
|
857
|
+
| 4 | `categories: ["pos", "neg"]` (array) | Use `categories: "pos,neg"` — comma-separated string |
|
|
858
|
+
| 5 | `parallel: 50` (too high) | Use `parallel: 3` to `5` — higher causes rate limiting |
|
|
859
|
+
| 6 | Confusing `field` and `input_key` | `field` = WHAT to extract from parent; `input_key` = WHERE to put it in API call |
|
|
860
|
+
| 7 | `type: llm` without `dependency` | LLM sources require `dependency` (parent source) and `llm` (>= 1 step) |
|
|
861
|
+
| 8 | SQL query uses `search-results` view name | Hyphens → underscores in DuckDB views: use `search_results` |
|
|
862
|
+
|
|
863
|
+
**Validate catches most errors:**
|
|
864
|
+
```bash
|
|
865
|
+
anysite dataset validate dataset.yaml --json
|
|
866
|
+
# "Endpoint must start with '/'" → add leading /
|
|
867
|
+
# "enrich step requires 'add'" → add 'add' list to enrich step
|
|
868
|
+
# "LLM source must have a dependency" → add dependency block
|
|
869
|
+
# "did you mean '=='?" → replace = with == in filter
|
|
870
|
+
```
|