@n1k1t/pipelain 0.4.14 → 1.0.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/settings.local.json +7 -0
- package/AGENTS.md +20 -0
- package/README.md +185 -12
- package/assets/report/index.hbs +201 -79
- package/assets/report/main.css +135 -3
- package/cli/index.ts +77 -0
- package/lib/src/index.d.ts +2 -0
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js +2 -0
- package/lib/src/index.js.map +1 -1
- package/lib/src/models/content/factory.d.ts +7 -1
- package/lib/src/models/content/factory.d.ts.map +1 -1
- package/lib/src/models/content/factory.js +15 -0
- package/lib/src/models/content/factory.js.map +1 -1
- package/lib/src/models/content/kinds/article.d.ts +3 -1
- package/lib/src/models/content/kinds/article.d.ts.map +1 -1
- package/lib/src/models/content/kinds/article.js +7 -0
- package/lib/src/models/content/kinds/article.js.map +1 -1
- package/lib/src/models/content/kinds/attachment.d.ts +3 -1
- package/lib/src/models/content/kinds/attachment.d.ts.map +1 -1
- package/lib/src/models/content/kinds/attachment.js +7 -0
- package/lib/src/models/content/kinds/attachment.js.map +1 -1
- package/lib/src/models/content/kinds/group.d.ts +3 -1
- package/lib/src/models/content/kinds/group.d.ts.map +1 -1
- package/lib/src/models/content/kinds/group.js +7 -0
- package/lib/src/models/content/kinds/group.js.map +1 -1
- package/lib/src/models/content/kinds/model.d.ts +5 -0
- package/lib/src/models/content/kinds/model.d.ts.map +1 -1
- package/lib/src/models/content/kinds/model.js +6 -0
- package/lib/src/models/content/kinds/model.js.map +1 -1
- package/lib/src/models/content/kinds/plain.d.ts +3 -1
- package/lib/src/models/content/kinds/plain.d.ts.map +1 -1
- package/lib/src/models/content/kinds/plain.js +7 -0
- package/lib/src/models/content/kinds/plain.js.map +1 -1
- package/lib/src/models/content/kinds/rules.d.ts +3 -1
- package/lib/src/models/content/kinds/rules.d.ts.map +1 -1
- package/lib/src/models/content/kinds/rules.js +7 -0
- package/lib/src/models/content/kinds/rules.js.map +1 -1
- package/lib/src/models/content/kinds/sources.d.ts +3 -1
- package/lib/src/models/content/kinds/sources.d.ts.map +1 -1
- package/lib/src/models/content/kinds/sources.js +7 -0
- package/lib/src/models/content/kinds/sources.js.map +1 -1
- package/lib/src/models/content/kinds/tasks.d.ts +3 -1
- package/lib/src/models/content/kinds/tasks.d.ts.map +1 -1
- package/lib/src/models/content/kinds/tasks.js +7 -0
- package/lib/src/models/content/kinds/tasks.js.map +1 -1
- package/lib/src/models/index.d.ts +1 -0
- package/lib/src/models/index.d.ts.map +1 -1
- package/lib/src/models/index.js +1 -0
- package/lib/src/models/index.js.map +1 -1
- package/lib/src/models/llm/hook.d.ts +128 -0
- package/lib/src/models/llm/hook.d.ts.map +1 -0
- package/lib/src/models/llm/hook.js +129 -0
- package/lib/src/models/llm/hook.js.map +1 -0
- package/lib/src/models/llm/index.d.ts +1 -0
- package/lib/src/models/llm/index.d.ts.map +1 -1
- package/lib/src/models/llm/index.js +1 -0
- package/lib/src/models/llm/index.js.map +1 -1
- package/lib/src/models/llm/mcp.d.ts +4 -0
- package/lib/src/models/llm/mcp.d.ts.map +1 -1
- package/lib/src/models/llm/mcp.js +5 -2
- package/lib/src/models/llm/mcp.js.map +1 -1
- package/lib/src/models/llm/providers/model.d.ts +1 -1
- package/lib/src/models/llm/providers/model.d.ts.map +1 -1
- package/lib/src/models/llm/providers/proxy.d.ts +6 -0
- package/lib/src/models/llm/providers/proxy.d.ts.map +1 -1
- package/lib/src/models/llm/providers/proxy.js +5 -3
- package/lib/src/models/llm/providers/proxy.js.map +1 -1
- package/lib/src/models/llm/router.d.ts +6 -0
- package/lib/src/models/llm/router.d.ts.map +1 -1
- package/lib/src/models/llm/router.js +12 -0
- package/lib/src/models/llm/router.js.map +1 -1
- package/lib/src/models/llm/tools/attachment.js +2 -2
- package/lib/src/models/llm/tools/attachment.js.map +1 -1
- package/lib/src/models/llm/tools/edit.js +3 -3
- package/lib/src/models/llm/tools/edit.js.map +1 -1
- package/lib/src/models/llm/tools/fetch.js +4 -4
- package/lib/src/models/llm/tools/fetch.js.map +1 -1
- package/lib/src/models/llm/tools/grep.js +2 -2
- package/lib/src/models/llm/tools/grep.js.map +1 -1
- package/lib/src/models/llm/tools/ls.js +2 -2
- package/lib/src/models/llm/tools/ls.js.map +1 -1
- package/lib/src/models/llm/tools/mkdir.js +2 -2
- package/lib/src/models/llm/tools/mkdir.js.map +1 -1
- package/lib/src/models/llm/tools/model.d.ts +4 -4
- package/lib/src/models/llm/tools/model.d.ts.map +1 -1
- package/lib/src/models/llm/tools/model.js +19 -10
- package/lib/src/models/llm/tools/model.js.map +1 -1
- package/lib/src/models/llm/tools/npm.js +1 -1
- package/lib/src/models/llm/tools/npm.js.map +1 -1
- package/lib/src/models/llm/tools/npx.js +1 -1
- package/lib/src/models/llm/tools/npx.js.map +1 -1
- package/lib/src/models/llm/tools/read.js +3 -3
- package/lib/src/models/llm/tools/read.js.map +1 -1
- package/lib/src/models/llm/tools/rm.js +4 -4
- package/lib/src/models/llm/tools/rm.js.map +1 -1
- package/lib/src/models/llm/tools/search.js +2 -2
- package/lib/src/models/llm/tools/search.js.map +1 -1
- package/lib/src/models/llm/tools/skill.js +2 -2
- package/lib/src/models/llm/tools/skill.js.map +1 -1
- package/lib/src/models/llm/tools/write.js +3 -3
- package/lib/src/models/llm/tools/write.js.map +1 -1
- package/lib/src/models/meta.d.ts +12 -0
- package/lib/src/models/meta.d.ts.map +1 -0
- package/lib/src/models/meta.js +32 -0
- package/lib/src/models/meta.js.map +1 -0
- package/lib/src/models/pipeline/model.d.ts +2 -0
- package/lib/src/models/pipeline/model.d.ts.map +1 -1
- package/lib/src/models/pipeline/model.js +8 -4
- package/lib/src/models/pipeline/model.js.map +1 -1
- package/lib/src/models/pipeline/parameters.d.ts.map +1 -1
- package/lib/src/models/pipeline/parameters.js +5 -1
- package/lib/src/models/pipeline/parameters.js.map +1 -1
- package/lib/src/models/pipeline/report/index.d.ts.map +1 -1
- package/lib/src/models/pipeline/report/index.js +28 -10
- package/lib/src/models/pipeline/report/index.js.map +1 -1
- package/lib/src/models/pipeline/report/types.d.ts +15 -7
- package/lib/src/models/pipeline/report/types.d.ts.map +1 -1
- package/lib/src/models/pipeline/session.d.ts +16 -36
- package/lib/src/models/pipeline/session.d.ts.map +1 -1
- package/lib/src/models/pipeline/session.js +15 -10
- package/lib/src/models/pipeline/session.js.map +1 -1
- package/lib/src/models/pipeline/stdout/compiled/console.js +16 -13
- package/lib/src/models/pipeline/stdout/compiled/console.js.map +1 -1
- package/lib/src/models/pipeline/stdout/model.d.ts +1 -1
- package/lib/src/models/pipeline/stdout/model.d.ts.map +1 -1
- package/lib/src/models/pipeline/stdout/model.js +12 -7
- package/lib/src/models/pipeline/stdout/model.js.map +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/fallback.d.ts +31 -0
- package/lib/src/models/pipeline/steps/ai/actions/fallback.d.ts.map +1 -0
- package/lib/src/models/pipeline/steps/ai/actions/fallback.js +51 -0
- package/lib/src/models/pipeline/steps/ai/actions/fallback.js.map +1 -0
- package/lib/src/models/pipeline/steps/ai/actions/index.d.ts +1 -0
- package/lib/src/models/pipeline/steps/ai/actions/index.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/index.js +1 -0
- package/lib/src/models/pipeline/steps/ai/actions/index.js.map +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/model.d.ts +3 -6
- package/lib/src/models/pipeline/steps/ai/actions/model.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/model.js +3 -11
- package/lib/src/models/pipeline/steps/ai/actions/model.js.map +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/reasoning.d.ts +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/reasoning.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/reasoning.js +6 -4
- package/lib/src/models/pipeline/steps/ai/actions/reasoning.js.map +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/tool.d.ts +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/tool.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/ai/actions/tool.js +4 -3
- package/lib/src/models/pipeline/steps/ai/actions/tool.js.map +1 -1
- package/lib/src/models/pipeline/steps/ai/index.d.ts +5 -1
- package/lib/src/models/pipeline/steps/ai/index.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/ai/index.js +134 -97
- package/lib/src/models/pipeline/steps/ai/index.js.map +1 -1
- package/lib/src/models/pipeline/steps/ai/types.d.ts +5 -1
- package/lib/src/models/pipeline/steps/ai/types.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/ai/utils.js +1 -1
- package/lib/src/models/pipeline/steps/ai/utils.js.map +1 -1
- package/lib/src/models/pipeline/steps/loop.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/loop.js +10 -7
- package/lib/src/models/pipeline/steps/loop.js.map +1 -1
- package/lib/src/models/pipeline/steps/model.d.ts +2 -0
- package/lib/src/models/pipeline/steps/model.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/model.js +2 -0
- package/lib/src/models/pipeline/steps/model.js.map +1 -1
- package/lib/src/models/pipeline/steps/self.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/self.js +6 -5
- package/lib/src/models/pipeline/steps/self.js.map +1 -1
- package/lib/src/models/pipeline/steps/swarm.d.ts +13 -6
- package/lib/src/models/pipeline/steps/swarm.d.ts.map +1 -1
- package/lib/src/models/pipeline/steps/swarm.js +46 -23
- package/lib/src/models/pipeline/steps/swarm.js.map +1 -1
- package/lib/src/models/pipeline/types.d.ts +1 -0
- package/lib/src/models/pipeline/types.d.ts.map +1 -1
- package/lib/src/setup.js +30 -3
- package/lib/src/setup.js.map +1 -1
- package/lib/src/utils/common.d.ts +8 -0
- package/lib/src/utils/common.d.ts.map +1 -1
- package/lib/src/utils/common.js +14 -1
- package/lib/src/utils/common.js.map +1 -1
- package/lib/src/utils/index.d.ts +0 -1
- package/lib/src/utils/index.d.ts.map +1 -1
- package/lib/src/utils/index.js +0 -1
- package/lib/src/utils/index.js.map +1 -1
- package/package.json +13 -12
- package/lib/src/utils/meta.d.ts +0 -9
- package/lib/src/utils/meta.d.ts.map +0 -1
- package/lib/src/utils/meta.js +0 -15
- package/lib/src/utils/meta.js.map +0 -1
package/AGENTS.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Instructions for AI agents working in this repository (`@n1k1t/pipelain`).
|
|
4
|
+
|
|
5
|
+
## Mandatory rules
|
|
6
|
+
|
|
7
|
+
- If the `caveman` skill is installed, use it for communication in this session.
|
|
8
|
+
|
|
9
|
+
## Project documentation
|
|
10
|
+
|
|
11
|
+
Before starting work, review the documentation in `.ai/docs`:
|
|
12
|
+
|
|
13
|
+
- [`.ai/docs/structure.md`](.ai/docs/structure.md) — project structure, purpose of the main directories and files.
|
|
14
|
+
- [`.ai/docs/style.md`](.ai/docs/style.md) — code style: import/export order, object and function formatting.
|
|
15
|
+
|
|
16
|
+
## Useful commands
|
|
17
|
+
|
|
18
|
+
- `npm test` — run tests (Jest).
|
|
19
|
+
- `npm run build:check` — type-check without building (`tsc --noEmit`).
|
|
20
|
+
- `npm run build` — build the project into `lib/`.
|
package/README.md
CHANGED
|
@@ -33,14 +33,17 @@ Powerful utility to build and execute type-safe AI pipelines with structured out
|
|
|
33
33
|
- [AI Step with LLM Configuration](#ai-step-with-llm-configuration)
|
|
34
34
|
- [AI Step with Fallback](#ai-step-with-fallback)
|
|
35
35
|
- [Debugging AI Steps](#debugging-ai-steps)
|
|
36
|
+
- [Execution Reports](#execution-reports)
|
|
36
37
|
- [Combining self and ai Steps](#combining-self-and-ai-steps)
|
|
37
38
|
- [Parallel Execution with swarm](#parallel-execution-with-swarm)
|
|
38
39
|
- [Iterative Execution with loop](#iterative-execution-with-loop)
|
|
39
40
|
- [Multi-Provider Pipelines](#multi-provider-pipelines)
|
|
41
|
+
- [Custom Model Routing](#custom-model-routing)
|
|
40
42
|
- [MCP (Model Context Protocol) Integration](#mcp-model-context-protocol-integration)
|
|
41
43
|
- [Using Skills](#using-skills)
|
|
42
44
|
- [Extensions](#extensions)
|
|
43
45
|
- [Custom LLM Tools](#custom-llm-tools)
|
|
46
|
+
- [LLM Tool Hooks](#llm-tool-hooks)
|
|
44
47
|
- [Custom LLM Skills](#custom-llm-skills)
|
|
45
48
|
- [Registering Skills from Markdown](#registering-skills-from-markdown)
|
|
46
49
|
- [License](#license)
|
|
@@ -114,9 +117,11 @@ The following environment variables are supported:
|
|
|
114
117
|
| --- | --- | --- |
|
|
115
118
|
| `PIPELAIN_API_KEY` | API key for the LLM provider. | - |
|
|
116
119
|
| `PIPELAIN_API_URL` | Custom API URL for the LLM provider. | - |
|
|
117
|
-
| `PIPELAIN_SKILLS_PATHS` | Directories paths where LLM skills are stored (separated by `;`). |
|
|
120
|
+
| `PIPELAIN_SKILLS_PATHS` | Directories paths where LLM skills are stored (separated by `;`). | `./.agents/skills;~/.agents/skills` |
|
|
118
121
|
| `PIPELAIN_MODEL` | Default LLM model to use. | `gemini-flash-latest` |
|
|
119
122
|
| `PIPELAIN_PROVIDER` | LLM provider name (e.g., `google`, `openai`). | - |
|
|
123
|
+
| `PIPELAIN_FLAGS_REPORT` | Save an HTML report of the pipeline execution. | `false` |
|
|
124
|
+
| `PIPELAIN_FLAGS_DEBUG` | Enable debug mode for all AI steps. | `false` |
|
|
120
125
|
| `EXA_API_KEY` | API key for Exa web search tools. | - |
|
|
121
126
|
|
|
122
127
|
## Pipeline Step Utilities
|
|
@@ -136,7 +141,7 @@ Used to create different types of steps:
|
|
|
136
141
|
General-purpose utilities:
|
|
137
142
|
- `content`: `ContentFactory` instance to create structured prompt content (articles, tasks, rules, attachments).
|
|
138
143
|
- `bash`: Execute shell commands on the local machine.
|
|
139
|
-
- `log`: Emit log events for the current pipeline session.
|
|
144
|
+
- `log`: Emit `INFO` log events for the current pipeline session (shown in stdout and HTML reports).
|
|
140
145
|
|
|
141
146
|
### `context`
|
|
142
147
|
Shared state and configuration:
|
|
@@ -158,6 +163,10 @@ Used to create structured prompt content. These methods are available via `utils
|
|
|
158
163
|
| `file(title, path)` | Reads a local file and attaches it as context. | `await utils.content.file('Config', 'package.json')` |
|
|
159
164
|
| `glob(title, pattern)` | Reads multiple files by pattern and attaches them. | `await utils.content.glob('Source', 'src/**/*.ts')` |
|
|
160
165
|
| `plain(text)` | Adds raw markdown text. | `utils.content.plain('### Subtitle\nText')` |
|
|
166
|
+
| `user(content)` | Forces content to be placed into the `user` message. | `utils.content.user('Say hi')` |
|
|
167
|
+
| `system(content)` | Forces content to be placed into the `system` message. | `utils.content.system('You are a helpful assistant')` |
|
|
168
|
+
|
|
169
|
+
Each content kind has a default location it is rendered into (`tasks` and plain strings default to `user`, everything else defaults to `system`). Wrap any content in `utils.content.user()` / `utils.content.system()` to override that placement explicitly.
|
|
161
170
|
|
|
162
171
|
### `factory.tools` (LlmToolsFactory)
|
|
163
172
|
Used to provide tools to the AI. These methods are available via `factory.tools` in pipeline steps.
|
|
@@ -205,13 +214,13 @@ To use a custom logger or override specific event handlers, use `PipelineStdout`
|
|
|
205
214
|
import { PipelineStdout } from '@n1k1t/pipelain';
|
|
206
215
|
|
|
207
216
|
const customStdout = PipelineStdout
|
|
208
|
-
.build(console) // Pass any logger with .info and .
|
|
209
|
-
.override('log', (
|
|
210
|
-
console.log(`[
|
|
217
|
+
.build(console) // Pass any logger with .info, .warn and .debug methods
|
|
218
|
+
.override('log', ({ level, message }) => {
|
|
219
|
+
console.log(`[${level}]`, ...message);
|
|
211
220
|
})
|
|
212
|
-
.override('step:run', (
|
|
213
|
-
if (
|
|
214
|
-
console.log(`Step ${
|
|
221
|
+
.override('step:run', ({ step }) => {
|
|
222
|
+
if (step.meta.is('DONE')) {
|
|
223
|
+
console.log(`Step ${step.title} finished in ${step.meta.spent}ms`);
|
|
215
224
|
}
|
|
216
225
|
});
|
|
217
226
|
|
|
@@ -221,6 +230,17 @@ const customStdout = PipelineStdout
|
|
|
221
230
|
})();
|
|
222
231
|
```
|
|
223
232
|
|
|
233
|
+
Each pipeline, step and AI action has a `meta` object with its execution state:
|
|
234
|
+
|
|
235
|
+
| Property / Method | Description |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| `state` | `'INIT' \| 'PENDING' \| 'DONE' \| 'ERROR'` |
|
|
238
|
+
| `spent` | Time spent since creation (ms). |
|
|
239
|
+
| `timestamp` | Creation timestamp. |
|
|
240
|
+
| `is(state \| state[])` | Checks the current state. |
|
|
241
|
+
|
|
242
|
+
The `log` event has a `level` field: `'DEBUG' | 'INFO' | 'WARN'`. `utils.log(...)` emits `INFO`.
|
|
243
|
+
|
|
224
244
|
### Structured Prompt Content
|
|
225
245
|
|
|
226
246
|
You can combine multiple content types to create a rich, structured prompt for the AI:
|
|
@@ -349,7 +369,28 @@ You can use the `.debug()` method to inspect the prompts sent to the AI. When de
|
|
|
349
369
|
)
|
|
350
370
|
```
|
|
351
371
|
|
|
352
|
-
The prompt will be saved to: `.pipelain
|
|
372
|
+
The prompt will be saved to: `.pipelain/debug/YYYY-MM-DD--HH-mm-ss--<session-id>/<step-number>.<step-trace>.md`.
|
|
373
|
+
|
|
374
|
+
### Execution Reports
|
|
375
|
+
|
|
376
|
+
Use `.report()` on the pipeline compiler (or set `PIPELAIN_FLAGS_REPORT=true`) to save an HTML report of the AI steps execution:
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
const pipeline = PipelineCompiler
|
|
380
|
+
.build('Translate')
|
|
381
|
+
.report() // Enables HTML report
|
|
382
|
+
.input(z.string())
|
|
383
|
+
.step('translated', ({ factory, context }) => factory
|
|
384
|
+
.ai('Translating')
|
|
385
|
+
.prompt([`Translate "${context.input}" into Spanish`])
|
|
386
|
+
);
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
The report is saved into `.pipelain/reports/YYYY-MM-DD--HH-mm-ss--<session-id>.html` and includes:
|
|
390
|
+
|
|
391
|
+
- AI steps with messages, actions (tools, reasoning, fallbacks), output and timing;
|
|
392
|
+
- `INFO` and `WARN` logs emitted via `utils.log`;
|
|
393
|
+
- token usage per provider and model (prompt, cached, completion) and total session time.
|
|
353
394
|
|
|
354
395
|
### Combining `self` and `ai` Steps
|
|
355
396
|
|
|
@@ -389,12 +430,14 @@ const pipeline = PipelineCompiler
|
|
|
389
430
|
|
|
390
431
|
### Parallel Execution with `swarm`
|
|
391
432
|
|
|
392
|
-
Use `swarm` to execute multiple AI tasks in parallel. You can define subtasks
|
|
433
|
+
Use `swarm` to execute multiple AI tasks in parallel. You can define subtasks as a `list` (array) or as a `map` (keyed object), optionally limiting how many run concurrently with `.limit()`.
|
|
434
|
+
|
|
435
|
+
#### `list`
|
|
393
436
|
|
|
394
437
|
```ts
|
|
395
438
|
.step('analysis', ({ factory }) => factory
|
|
396
439
|
.swarm('Parallel Analysis')
|
|
397
|
-
.
|
|
440
|
+
.list([
|
|
398
441
|
factory
|
|
399
442
|
.ai('Sentiment Analysis')
|
|
400
443
|
.schema(z.object({ score: z.number() }))
|
|
@@ -410,6 +453,32 @@ Use `swarm` to execute multiple AI tasks in parallel. You can define subtasks us
|
|
|
410
453
|
// Results will be available in context.state.analysis as an array of PromiseSettledResult
|
|
411
454
|
```
|
|
412
455
|
|
|
456
|
+
> `subtasks(...)` is a deprecated alias of `list(...)` kept for backward compatibility — prefer `list`.
|
|
457
|
+
|
|
458
|
+
#### `map`
|
|
459
|
+
|
|
460
|
+
Use `map` when you need to address each subtask result by name instead of by array index:
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
.step('analysis', ({ factory }) => factory
|
|
464
|
+
.swarm('Parallel Analysis')
|
|
465
|
+
.map({
|
|
466
|
+
sentiment: factory
|
|
467
|
+
.ai('Sentiment Analysis')
|
|
468
|
+
.schema(z.object({ score: z.number() }))
|
|
469
|
+
.prompt(({ context }) => [`Analyze sentiment of: ${context.input}`]),
|
|
470
|
+
|
|
471
|
+
keywords: factory
|
|
472
|
+
.ai('Keyword Extraction')
|
|
473
|
+
.schema(z.object({ tags: z.array(z.string()) }))
|
|
474
|
+
.prompt(({ context }) => [`Extract keywords from: ${context.input}`]),
|
|
475
|
+
})
|
|
476
|
+
.limit(2) // Optional: limit parallel executions
|
|
477
|
+
)
|
|
478
|
+
// context.state.analysis.sentiment -> PromiseSettledResult<{ score: number }>
|
|
479
|
+
// context.state.analysis.keywords -> PromiseSettledResult<{ tags: string[] }>
|
|
480
|
+
```
|
|
481
|
+
|
|
413
482
|
### Iterative Execution with `loop`
|
|
414
483
|
|
|
415
484
|
Use `loop` for tasks that require multiple iterations or validation, such as self-correction:
|
|
@@ -472,6 +541,29 @@ const pipeline = PipelineCompiler
|
|
|
472
541
|
);
|
|
473
542
|
```
|
|
474
543
|
|
|
544
|
+
### Custom Model Routing
|
|
545
|
+
|
|
546
|
+
`LlmRouter` picks a provider by model name. Use `register` to route models matched by a [minimatch](https://github.com/isaacs/minimatch) pattern to your own provider:
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
import { LlmRouter, llm } from '@n1k1t/pipelain';
|
|
550
|
+
|
|
551
|
+
const router = LlmRouter
|
|
552
|
+
.build()
|
|
553
|
+
.register('corp-*', (model) => llm.providers.LlmProxyProvider.build(model, {
|
|
554
|
+
name: 'corp', // Shown in usage stats and reports instead of "proxy"
|
|
555
|
+
connection: { key: process.env.CORP_API_KEY!, url: 'https://llm.corp.local/v1' },
|
|
556
|
+
}));
|
|
557
|
+
|
|
558
|
+
pipeline.step('analysis', ({ factory }) => factory
|
|
559
|
+
.ai('Corp Analysis')
|
|
560
|
+
.llm(() => router.provide('corp-gpt-4o'))
|
|
561
|
+
.prompt(['...'])
|
|
562
|
+
);
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Registrations are checked in order and the first match wins. Unmatched models fall back to the default routing.
|
|
566
|
+
|
|
475
567
|
### MCP (Model Context Protocol) Integration
|
|
476
568
|
|
|
477
569
|
You can integrate MCP servers into your pipeline steps. This allows the AI to use tools provided by external MCP servers. You can also filter which tools are enabled:
|
|
@@ -494,6 +586,9 @@ pipeline.step('mcp_research', ({ factory, context }) => factory
|
|
|
494
586
|
enabled: ['list-files', 'read-file'],
|
|
495
587
|
// Or disable specific tools
|
|
496
588
|
// disabled: ['delete-file'],
|
|
589
|
+
|
|
590
|
+
// Add prefix to tool names (e.g. "gdrive_read-file")
|
|
591
|
+
prefix: 'gdrive_',
|
|
497
592
|
},
|
|
498
593
|
}),
|
|
499
594
|
],
|
|
@@ -502,6 +597,8 @@ pipeline.step('mcp_research', ({ factory, context }) => factory
|
|
|
502
597
|
);
|
|
503
598
|
```
|
|
504
599
|
|
|
600
|
+
Use `tools.prefix` to avoid name conflicts between MCP servers or with built-in tools. The `enabled` and `disabled` patterns match tool names without the prefix. Hooks by name (`LlmHook.build('gdrive_read-file')`) match tool names with the prefix.
|
|
601
|
+
|
|
505
602
|
### Using Skills
|
|
506
603
|
|
|
507
604
|
Skills allow you to inject reusable domain-specific instructions or workflows into your AI steps. You can manage them via `factory.skills`:
|
|
@@ -530,7 +627,7 @@ You can create custom tools for the AI using `LlmToolCompiler`. This allows the
|
|
|
530
627
|
|
|
531
628
|
```ts
|
|
532
629
|
import z from 'zod/v3';
|
|
533
|
-
import { LlmToolCompiler } from '@n1k1t/pipelain';
|
|
630
|
+
import { LlmToolCompiler, LlmToolExecutionError } from '@n1k1t/pipelain';
|
|
534
631
|
|
|
535
632
|
// 1. Define the tool
|
|
536
633
|
const weatherTool = LlmToolCompiler
|
|
@@ -543,6 +640,11 @@ const weatherTool = LlmToolCompiler
|
|
|
543
640
|
condition: z.string()
|
|
544
641
|
}))
|
|
545
642
|
.execute(() => async ({ city }) => {
|
|
643
|
+
if (!city.trim()) {
|
|
644
|
+
// Errors are reported back to the AI as "Execution failed: <reason>"
|
|
645
|
+
throw LlmToolExecutionError.build('City name is empty');
|
|
646
|
+
}
|
|
647
|
+
|
|
546
648
|
// Your implementation here
|
|
547
649
|
return { temperature: 22, condition: 'Sunny' };
|
|
548
650
|
});
|
|
@@ -560,6 +662,77 @@ pipeline.step('weather_report', ({ factory, context }) => factory
|
|
|
560
662
|
);
|
|
561
663
|
```
|
|
562
664
|
|
|
665
|
+
### LLM Tool Hooks
|
|
666
|
+
|
|
667
|
+
Use `LlmHook` to intercept tool calls inside an `ai` step. With a hook you can validate or modify the input, override the output, skip the original execution or handle errors. Provide hooks to the step with the `.hooks([...])` method.
|
|
668
|
+
|
|
669
|
+
A hook matches a tool by one of these targets:
|
|
670
|
+
|
|
671
|
+
- `LlmHook.build('name')` matches a tool by its name (built-in, custom or MCP tool).
|
|
672
|
+
- `LlmHook.build(compiler)` matches a tool compiled from the same `LlmToolCompiler` instance.
|
|
673
|
+
- `LlmHook.build(tool)` matches the same `Tool` instance.
|
|
674
|
+
|
|
675
|
+
When a hook has a name (`LlmHook.build(compiler, { name: 'weather' })`), the name has priority over the compiler or tool.
|
|
676
|
+
|
|
677
|
+
Each hook has two handlers:
|
|
678
|
+
|
|
679
|
+
- `before({ input, next, session, context, step })` runs instead of the original `execute`. The original `execute` runs only when you call `next(input)`. If you do not call `next`, the returned value becomes the tool output.
|
|
680
|
+
- `after({ output, input, session, context, step })` runs after `before` is settled. `output` is a `PromiseSettledResult`, so you can handle errors. The returned value becomes the final tool output and must match the tool output type. By default, `after` rethrows the error or returns the value.
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
import { LlmHook, LlmToolExecutionError } from '@n1k1t/pipelain';
|
|
684
|
+
|
|
685
|
+
// Restrict paths of the built-in "write" tool
|
|
686
|
+
const guard = LlmHook
|
|
687
|
+
.build<{ input: { path: string; content: string } }>('write')
|
|
688
|
+
.before(({ input, next }) => {
|
|
689
|
+
if (input.path.startsWith('/etc')) {
|
|
690
|
+
throw LlmToolExecutionError.build('Writing to /etc is not allowed');
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return next(input);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
// Cache results of the custom tool and convert errors for the AI
|
|
697
|
+
const cache = new Map<string, { temperature: number; condition: string }>();
|
|
698
|
+
const weather = LlmHook
|
|
699
|
+
.build(weatherTool)
|
|
700
|
+
.before(async ({ input, next }) => {
|
|
701
|
+
const cached = cache.get(input.city);
|
|
702
|
+
if (cached) {
|
|
703
|
+
return cached;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const output = await next(input);
|
|
707
|
+
|
|
708
|
+
cache.set(input.city, output);
|
|
709
|
+
return output;
|
|
710
|
+
})
|
|
711
|
+
.after(({ output }) => {
|
|
712
|
+
if (output.status === 'rejected') {
|
|
713
|
+
throw LlmToolExecutionError.build(output.reason);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return output.value;
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
pipeline.step('hooked', ({ factory, context }) => factory
|
|
720
|
+
.ai('Executing with hooks')
|
|
721
|
+
.llm(({ context }) => context.llm.assign({
|
|
722
|
+
tools: factory.tools
|
|
723
|
+
.files('read-write')
|
|
724
|
+
.custom({ weather: weatherTool })
|
|
725
|
+
.provide(),
|
|
726
|
+
}))
|
|
727
|
+
.hooks([guard, weather])
|
|
728
|
+
.prompt([`Check weather in ${context.input} and save it to weather.md`])
|
|
729
|
+
);
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
When several hooks match the same tool, they run in the order of the array: the `before` handler of the first hook runs first, and its `after` handler runs last.
|
|
733
|
+
|
|
734
|
+
> Built-in tools with options (for example, `write` in `files('read-write')`) are clones of the original compiler. Match built-in tools by name.
|
|
735
|
+
|
|
563
736
|
### Custom LLM Skills
|
|
564
737
|
|
|
565
738
|
You can also provide custom skills programmatically:
|