@bpmnkit/casen-worker-ai 0.1.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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-check.log +5 -0
- package/.turbo/turbo-test.log +18 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +35 -0
- package/dist/config.test.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.d.ts +18 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +57 -0
- package/dist/llm.js.map +1 -0
- package/dist/operations/classify.d.ts +16 -0
- package/dist/operations/classify.d.ts.map +1 -0
- package/dist/operations/classify.js +74 -0
- package/dist/operations/classify.js.map +1 -0
- package/dist/operations/classify.test.d.ts +2 -0
- package/dist/operations/classify.test.d.ts.map +1 -0
- package/dist/operations/classify.test.js +74 -0
- package/dist/operations/classify.test.js.map +1 -0
- package/dist/operations/decide.d.ts +16 -0
- package/dist/operations/decide.d.ts.map +1 -0
- package/dist/operations/decide.js +60 -0
- package/dist/operations/decide.js.map +1 -0
- package/dist/operations/decide.test.d.ts +2 -0
- package/dist/operations/decide.test.d.ts.map +1 -0
- package/dist/operations/decide.test.js +65 -0
- package/dist/operations/decide.test.js.map +1 -0
- package/dist/operations/extract.d.ts +16 -0
- package/dist/operations/extract.d.ts.map +1 -0
- package/dist/operations/extract.js +70 -0
- package/dist/operations/extract.js.map +1 -0
- package/dist/operations/extract.test.d.ts +2 -0
- package/dist/operations/extract.test.d.ts.map +1 -0
- package/dist/operations/extract.test.js +65 -0
- package/dist/operations/extract.test.js.map +1 -0
- package/dist/operations/summarize.d.ts +16 -0
- package/dist/operations/summarize.d.ts.map +1 -0
- package/dist/operations/summarize.js +59 -0
- package/dist/operations/summarize.js.map +1 -0
- package/dist/operations/summarize.test.d.ts +2 -0
- package/dist/operations/summarize.test.d.ts.map +1 -0
- package/dist/operations/summarize.test.js +42 -0
- package/dist/operations/summarize.test.js.map +1 -0
- package/package.json +69 -0
- package/src/config.test.ts +40 -0
- package/src/config.ts +27 -0
- package/src/index.ts +54 -0
- package/src/llm.ts +67 -0
- package/src/operations/classify.test.ts +101 -0
- package/src/operations/classify.ts +85 -0
- package/src/operations/decide.test.ts +82 -0
- package/src/operations/decide.ts +70 -0
- package/src/operations/extract.test.ts +80 -0
- package/src/operations/extract.ts +80 -0
- package/src/operations/summarize.test.ts +48 -0
- package/src/operations/summarize.ts +68 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
> @bpmnkit/casen-worker-ai@0.1.0 test /home/adam/github.com/bpmnkit/monorepo/plugins-cli/casen-worker-ai
|
|
3
|
+
> vitest run
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
RUN v3.2.4 /home/adam/github.com/bpmnkit/monorepo/plugins-cli/casen-worker-ai
|
|
7
|
+
|
|
8
|
+
✓ src/config.test.ts (3 tests) 11ms
|
|
9
|
+
✓ src/operations/summarize.test.ts (3 tests) 11ms
|
|
10
|
+
✓ src/operations/extract.test.ts (5 tests) 20ms
|
|
11
|
+
✓ src/operations/classify.test.ts (7 tests) 7ms
|
|
12
|
+
✓ src/operations/decide.test.ts (5 tests) 16ms
|
|
13
|
+
|
|
14
|
+
Test Files 5 passed (5)
|
|
15
|
+
Tests 23 passed (23)
|
|
16
|
+
Start at 13:49:38
|
|
17
|
+
Duration 1.09s (transform 1.29s, setup 0ms, collect 2.21s, tests 65ms, environment 5ms, prepare 814ms)
|
|
18
|
+
|
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 urbanisierung
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://bpmnkit.com"><img src="https://bpmnkit.com/favicon.svg" width="72" height="72" alt="BPMN Kit logo"></a>
|
|
3
|
+
<h1>@bpmnkit/casen-worker-ai</h1>
|
|
4
|
+
<p>AI task worker plugin for casen — classify, summarize, extract, and decide using Claude</p>
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/@bpmnkit/casen-worker-ai)
|
|
7
|
+
[](https://github.com/bpmnkit/monorepo/blob/main/LICENSE)
|
|
8
|
+
[](https://github.com/bpmnkit/monorepo)
|
|
9
|
+
|
|
10
|
+
[Website](https://bpmnkit.com) · [Documentation](https://docs.bpmnkit.com) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/plugins-cli/casen-worker-ai/CHANGELOG.md)
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
`casen-worker-ai` is an official `casen` CLI plugin that brings AI-powered task processing into Camunda 8 workflows. It subscribes to four job types and uses the [Anthropic Claude API](https://anthropic.com) to complete each job with structured output.
|
|
18
|
+
|
|
19
|
+
This is the right tool when you need AI logic in a process but don't want to build a full microservice — start the worker once and any process in your cluster can delegate AI tasks to it.
|
|
20
|
+
|
|
21
|
+
## Why a worker instead of the HTTP connector?
|
|
22
|
+
|
|
23
|
+
The built-in Camunda HTTP connector can call any API but cannot:
|
|
24
|
+
- Validate or parse the response and fail the job if the output is malformed
|
|
25
|
+
- Route Anthropic rate-limit errors (429) to Camunda's retry budget with back-off
|
|
26
|
+
- Throw a typed BPMN error that an error boundary event can catch
|
|
27
|
+
|
|
28
|
+
The worker handles all three via `failJob` and `throwJobError`, giving your process model clean error paths.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
casen plugin install casen-worker-ai
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Set your Anthropic API key before starting:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
40
|
+
casen ai-worker
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
| Environment variable | Default | Description |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `ANTHROPIC_API_KEY` | **required** | Your Anthropic API key |
|
|
48
|
+
| `AI_MODEL` | `claude-3-5-haiku-20241022` | Model ID to use |
|
|
49
|
+
| `AI_MAX_TOKENS` | `1024` | Token budget per call |
|
|
50
|
+
| `AI_TIMEOUT_MS` | `60000` | HTTP timeout in ms |
|
|
51
|
+
|
|
52
|
+
## Operations
|
|
53
|
+
|
|
54
|
+
### `com.bpmnkit.ai.classify` — Classify text
|
|
55
|
+
|
|
56
|
+
Classifies text into one of the provided categories.
|
|
57
|
+
|
|
58
|
+
**Input variables:**
|
|
59
|
+
|
|
60
|
+
| Variable | Type | Required | Description |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| `input` | string | ✓ | Text to classify |
|
|
63
|
+
| `categories` | string[] | ✓ | Allowed category names |
|
|
64
|
+
| `context` | string | — | Optional domain context |
|
|
65
|
+
|
|
66
|
+
**Output variables:** `category`, `confidence` (0–1), `rationale`, `aiModel`, `processedAt`
|
|
67
|
+
|
|
68
|
+
**BPMN error codes:** `AI_INVALID_CATEGORY`, `AI_PARSE_ERROR`, `AI_API_ERROR`, `AI_INVALID_INPUT`
|
|
69
|
+
|
|
70
|
+
```xml
|
|
71
|
+
<bpmn:serviceTask id="ClassifyTicket" name="Classify ticket">
|
|
72
|
+
<bpmn:extensionElements>
|
|
73
|
+
<zeebe:taskDefinition type="com.bpmnkit.ai.classify" retries="3" />
|
|
74
|
+
<zeebe:taskHeaders>
|
|
75
|
+
<zeebe:header key="retryBackoff" value="PT30S" />
|
|
76
|
+
</zeebe:taskHeaders>
|
|
77
|
+
<zeebe:ioMapping>
|
|
78
|
+
<zeebe:input source="=ticket.body" target="input" />
|
|
79
|
+
<zeebe:input source='=["billing","technical","cancellation","general"]' target="categories" />
|
|
80
|
+
</zeebe:ioMapping>
|
|
81
|
+
</bpmn:extensionElements>
|
|
82
|
+
</bpmn:serviceTask>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `com.bpmnkit.ai.summarize` — Summarize text
|
|
86
|
+
|
|
87
|
+
Summarizes text to a target length and style.
|
|
88
|
+
|
|
89
|
+
**Input variables:**
|
|
90
|
+
|
|
91
|
+
| Variable | Type | Required | Description |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `input` | string | ✓ | Text to summarize |
|
|
94
|
+
| `maxWords` | number | — | Target word count (default: 100) |
|
|
95
|
+
| `style` | `"bullet"` | `"paragraph"` | — | Output style (default: `"paragraph"`) |
|
|
96
|
+
|
|
97
|
+
**Output variables:** `summary`, `wordCount`, `aiModel`, `processedAt`
|
|
98
|
+
|
|
99
|
+
### `com.bpmnkit.ai.extract` — Extract structured fields
|
|
100
|
+
|
|
101
|
+
Extracts named fields from unstructured text. Missing fields are reported but the job still completes — use a gateway on `=missingFields` to decide whether to auto-proceed or route to manual review.
|
|
102
|
+
|
|
103
|
+
**Input variables:**
|
|
104
|
+
|
|
105
|
+
| Variable | Type | Required | Description |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| `input` | string | ✓ | Unstructured text |
|
|
108
|
+
| `fields` | string[] | ✓ | Field names to extract |
|
|
109
|
+
| `schema` | object | — | Type hints per field, e.g. `{ amount: "number" }` |
|
|
110
|
+
|
|
111
|
+
**Output variables:** `extracted` (object), `missingFields` (array), `aiModel`, `processedAt`
|
|
112
|
+
|
|
113
|
+
### `com.bpmnkit.ai.decide` — Make a boolean decision
|
|
114
|
+
|
|
115
|
+
Answers a yes/no question based on context and an optional policy statement. Use `confidence` at a gateway to route low-confidence decisions to human review.
|
|
116
|
+
|
|
117
|
+
**Input variables:**
|
|
118
|
+
|
|
119
|
+
| Variable | Type | Required | Description |
|
|
120
|
+
|---|---|---|---|
|
|
121
|
+
| `question` | string | ✓ | The yes/no question |
|
|
122
|
+
| `context` | string | ✓ | Relevant facts |
|
|
123
|
+
| `policy` | string | — | Natural language policy the model must apply |
|
|
124
|
+
|
|
125
|
+
**Output variables:** `decision` (boolean), `rationale`, `confidence` (0–1), `aiModel`, `processedAt`
|
|
126
|
+
|
|
127
|
+
## Example workflow: Credit decision
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Start Event
|
|
131
|
+
→ summarize (summarize application notes, maxWords=150)
|
|
132
|
+
→ decide (question="Approve loan?", policy="Score ≥ 680 and amount ≤ 50k")
|
|
133
|
+
→ Gateway: decision=true and confidence≥0.8 → Auto-approve
|
|
134
|
+
decision=false and confidence≥0.8 → Auto-deny
|
|
135
|
+
default → Human review
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Error handling
|
|
139
|
+
|
|
140
|
+
Add boundary events to your service tasks to handle AI failures gracefully:
|
|
141
|
+
|
|
142
|
+
- **Error boundary** catching `AI_PARSE_ERROR` → route to a manual input fallback task
|
|
143
|
+
- **Timer boundary** (PT5M) → escalation path if the worker is not running
|
|
144
|
+
- Set `retries="3"` and `retryBackoff="PT30S"` on the task definition — the worker signals Camunda to retry via `failJob` on rate limits and network timeouts
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Related Packages
|
|
149
|
+
|
|
150
|
+
| Package | Description |
|
|
151
|
+
|---------|-------------|
|
|
152
|
+
| [`@bpmnkit/core`](https://www.npmjs.com/package/@bpmnkit/core) | BPMN/DMN/Form parser, builder, layout engine |
|
|
153
|
+
| [`@bpmnkit/canvas`](https://www.npmjs.com/package/@bpmnkit/canvas) | Zero-dependency SVG BPMN viewer |
|
|
154
|
+
| [`@bpmnkit/editor`](https://www.npmjs.com/package/@bpmnkit/editor) | Full-featured interactive BPMN editor |
|
|
155
|
+
| [`@bpmnkit/engine`](https://www.npmjs.com/package/@bpmnkit/engine) | Lightweight BPMN process execution engine |
|
|
156
|
+
| [`@bpmnkit/feel`](https://www.npmjs.com/package/@bpmnkit/feel) | FEEL expression language parser & evaluator |
|
|
157
|
+
| [`@bpmnkit/plugins`](https://www.npmjs.com/package/@bpmnkit/plugins) | 22 composable canvas plugins |
|
|
158
|
+
| [`@bpmnkit/api`](https://www.npmjs.com/package/@bpmnkit/api) | Camunda 8 REST API TypeScript client |
|
|
159
|
+
| [`@bpmnkit/ascii`](https://www.npmjs.com/package/@bpmnkit/ascii) | Render BPMN diagrams as Unicode ASCII art |
|
|
160
|
+
| [`@bpmnkit/ui`](https://www.npmjs.com/package/@bpmnkit/ui) | Shared design tokens and UI components |
|
|
161
|
+
| [`@bpmnkit/profiles`](https://www.npmjs.com/package/@bpmnkit/profiles) | Shared auth, profile storage, and client factories for CLI & proxy |
|
|
162
|
+
| [`@bpmnkit/operate`](https://www.npmjs.com/package/@bpmnkit/operate) | Monitoring & operations frontend for Camunda clusters |
|
|
163
|
+
| [`@bpmnkit/connector-gen`](https://www.npmjs.com/package/@bpmnkit/connector-gen) | Generate connector templates from OpenAPI specs |
|
|
164
|
+
| [`@bpmnkit/cli`](https://www.npmjs.com/package/@bpmnkit/cli) | Camunda 8 command-line interface (casen) |
|
|
165
|
+
| [`@bpmnkit/proxy`](https://www.npmjs.com/package/@bpmnkit/proxy) | Local AI bridge and Camunda API proxy server |
|
|
166
|
+
| [`@bpmnkit/cli-sdk`](https://www.npmjs.com/package/@bpmnkit/cli-sdk) | Plugin authoring SDK for the casen CLI |
|
|
167
|
+
| [`@bpmnkit/create-casen-plugin`](https://www.npmjs.com/package/@bpmnkit/create-casen-plugin) | Scaffold a new casen CLI plugin in seconds |
|
|
168
|
+
| [`@bpmnkit/casen-report`](https://www.npmjs.com/package/@bpmnkit/casen-report) | HTML reports from Camunda 8 incident and SLA data |
|
|
169
|
+
| [`@bpmnkit/casen-worker-http`](https://www.npmjs.com/package/@bpmnkit/casen-worker-http) | Example HTTP worker plugin — completes jobs with live JSONPlaceholder API data |
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
[MIT](https://github.com/bpmnkit/monorepo/blob/main/LICENSE) © BPMN Kit — made by [u11g](https://u11g.com)
|
|
174
|
+
|
|
175
|
+
<div align="center">
|
|
176
|
+
<a href="https://bpmnkit.com"><img src="https://bpmnkit.com/favicon.svg" width="32" height="32" alt="BPMN Kit"></a>
|
|
177
|
+
</div>
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface AiWorkerConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
model: string;
|
|
4
|
+
maxTokens: number;
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolves config from environment variables.
|
|
9
|
+
* Throws immediately if ANTHROPIC_API_KEY is absent so the error surfaces at
|
|
10
|
+
* worker startup rather than mid-job.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveConfig(): AiWorkerConfig;
|
|
13
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,cAAc,CAc9C"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves config from environment variables.
|
|
3
|
+
* Throws immediately if ANTHROPIC_API_KEY is absent so the error surfaces at
|
|
4
|
+
* worker startup rather than mid-job.
|
|
5
|
+
*/
|
|
6
|
+
export function resolveConfig() {
|
|
7
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
throw new Error("ANTHROPIC_API_KEY is not set. Export it before starting the AI worker:\n" +
|
|
10
|
+
" export ANTHROPIC_API_KEY=sk-ant-...");
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
apiKey,
|
|
14
|
+
model: process.env.AI_MODEL ?? "claude-3-5-haiku-20241022",
|
|
15
|
+
maxTokens: Number(process.env.AI_MAX_TOKENS ?? "1024"),
|
|
16
|
+
timeoutMs: Number(process.env.AI_TIMEOUT_MS ?? "60000"),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAA;IAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACd,0EAA0E;YACzE,uCAAuC,CACxC,CAAA;IACF,CAAC;IACD,OAAO;QACN,MAAM;QACN,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,2BAA2B;QAC1D,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,MAAM,CAAC;QACtD,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC;KACvD,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { resolveConfig } from "./config.js";
|
|
3
|
+
describe("resolveConfig", () => {
|
|
4
|
+
const original = { ...process.env };
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
for (const key of ["ANTHROPIC_API_KEY", "AI_MODEL", "AI_MAX_TOKENS", "AI_TIMEOUT_MS"]) {
|
|
7
|
+
Reflect.deleteProperty(process.env, key);
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
Object.assign(process.env, original);
|
|
12
|
+
});
|
|
13
|
+
it("throws when ANTHROPIC_API_KEY is not set", () => {
|
|
14
|
+
expect(() => resolveConfig()).toThrow("ANTHROPIC_API_KEY");
|
|
15
|
+
});
|
|
16
|
+
it("returns defaults when only API key is set", () => {
|
|
17
|
+
process.env.ANTHROPIC_API_KEY = "sk-test";
|
|
18
|
+
const cfg = resolveConfig();
|
|
19
|
+
expect(cfg.apiKey).toBe("sk-test");
|
|
20
|
+
expect(cfg.model).toBe("claude-3-5-haiku-20241022");
|
|
21
|
+
expect(cfg.maxTokens).toBe(1024);
|
|
22
|
+
expect(cfg.timeoutMs).toBe(60000);
|
|
23
|
+
});
|
|
24
|
+
it("overrides defaults from env vars", () => {
|
|
25
|
+
process.env.ANTHROPIC_API_KEY = "sk-test";
|
|
26
|
+
process.env.AI_MODEL = "claude-opus-4-6";
|
|
27
|
+
process.env.AI_MAX_TOKENS = "2048";
|
|
28
|
+
process.env.AI_TIMEOUT_MS = "30000";
|
|
29
|
+
const cfg = resolveConfig();
|
|
30
|
+
expect(cfg.model).toBe("claude-opus-4-6");
|
|
31
|
+
expect(cfg.maxTokens).toBe(2048);
|
|
32
|
+
expect(cfg.timeoutMs).toBe(30000);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC9B,MAAM,QAAQ,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEnC,UAAU,CAAC,GAAG,EAAE;QACf,KAAK,MAAM,GAAG,IAAI,CAAC,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,CAAC,EAAE,CAAC;YACvF,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QACzC,CAAC;IACF,CAAC,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACd,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACpD,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,SAAS,CAAA;QACzC,MAAM,GAAG,GAAG,aAAa,EAAE,CAAA;QAC3B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAClC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAA;QACnD,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC3C,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,SAAS,CAAA;QACzC,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,iBAAiB,CAAA;QACxC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,MAAM,CAAA;QAClC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,OAAO,CAAA;QACnC,MAAM,GAAG,GAAG,aAAa,EAAE,CAAA;QAC3B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;QACzC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,kBAAkB,CAAA;AAOxE,QAAA,MAAM,MAAM,EAAE,WA4Cb,CAAA;AAED,eAAe,MAAM,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createWorkerCommand } from "@bpmnkit/cli-sdk";
|
|
2
|
+
import { resolveConfig } from "./config.js";
|
|
3
|
+
import { classify } from "./operations/classify.js";
|
|
4
|
+
import { decide } from "./operations/decide.js";
|
|
5
|
+
import { extract } from "./operations/extract.js";
|
|
6
|
+
import { summarize } from "./operations/summarize.js";
|
|
7
|
+
const plugin = {
|
|
8
|
+
id: "com.bpmnkit.casen-worker-ai",
|
|
9
|
+
name: "AI Worker",
|
|
10
|
+
version: "0.1.0",
|
|
11
|
+
groups: [
|
|
12
|
+
{
|
|
13
|
+
name: "ai-worker",
|
|
14
|
+
description: "AI-powered job workers — classify, summarize, extract, decide",
|
|
15
|
+
commands: [
|
|
16
|
+
createWorkerCommand({
|
|
17
|
+
jobType: "com.bpmnkit.ai.classify",
|
|
18
|
+
description: "Classify text into one of the given categories",
|
|
19
|
+
defaultVariables: { category: "unknown", confidence: 0, rationale: "no handler" },
|
|
20
|
+
async processJob(job) {
|
|
21
|
+
return classify(job, resolveConfig());
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
createWorkerCommand({
|
|
25
|
+
jobType: "com.bpmnkit.ai.summarize",
|
|
26
|
+
description: "Summarize text to a given length and style",
|
|
27
|
+
defaultVariables: { summary: "", wordCount: 0 },
|
|
28
|
+
async processJob(job) {
|
|
29
|
+
return summarize(job, resolveConfig());
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
createWorkerCommand({
|
|
33
|
+
jobType: "com.bpmnkit.ai.extract",
|
|
34
|
+
description: "Extract structured fields from unstructured text",
|
|
35
|
+
defaultVariables: { extracted: {}, missingFields: [] },
|
|
36
|
+
async processJob(job) {
|
|
37
|
+
return extract(job, resolveConfig());
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
createWorkerCommand({
|
|
41
|
+
jobType: "com.bpmnkit.ai.decide",
|
|
42
|
+
description: "Make a boolean decision based on a question, context, and optional policy",
|
|
43
|
+
defaultVariables: { decision: false, rationale: "", confidence: 0 },
|
|
44
|
+
async processJob(job) {
|
|
45
|
+
return decide(job, resolveConfig());
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
export default plugin;
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAErD,MAAM,MAAM,GAAgB;IAC3B,EAAE,EAAE,6BAA6B;IACjC,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE;QACP;YACC,IAAI,EAAE,WAAW;YACjB,WAAW,EAAE,+DAA+D;YAC5E,QAAQ,EAAE;gBACT,mBAAmB,CAAC;oBACnB,OAAO,EAAE,yBAAyB;oBAClC,WAAW,EAAE,gDAAgD;oBAC7D,gBAAgB,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE;oBACjF,KAAK,CAAC,UAAU,CAAC,GAAG;wBACnB,OAAO,QAAQ,CAAC,GAAG,EAAE,aAAa,EAAE,CAAC,CAAA;oBACtC,CAAC;iBACD,CAAC;gBACF,mBAAmB,CAAC;oBACnB,OAAO,EAAE,0BAA0B;oBACnC,WAAW,EAAE,4CAA4C;oBACzD,gBAAgB,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE;oBAC/C,KAAK,CAAC,UAAU,CAAC,GAAG;wBACnB,OAAO,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,CAAC,CAAA;oBACvC,CAAC;iBACD,CAAC;gBACF,mBAAmB,CAAC;oBACnB,OAAO,EAAE,wBAAwB;oBACjC,WAAW,EAAE,kDAAkD;oBAC/D,gBAAgB,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;oBACtD,KAAK,CAAC,UAAU,CAAC,GAAG;wBACnB,OAAO,OAAO,CAAC,GAAG,EAAE,aAAa,EAAE,CAAC,CAAA;oBACrC,CAAC;iBACD,CAAC;gBACF,mBAAmB,CAAC;oBACnB,OAAO,EAAE,uBAAuB;oBAChC,WAAW,EAAE,2EAA2E;oBACxF,gBAAgB,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE;oBACnE,KAAK,CAAC,UAAU,CAAC,GAAG;wBACnB,OAAO,MAAM,CAAC,GAAG,EAAE,aAAa,EAAE,CAAC,CAAA;oBACpC,CAAC;iBACD,CAAC;aACF;SACD;KACD;CACD,CAAA;AAED,eAAe,MAAM,CAAA"}
|
package/dist/llm.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AiWorkerConfig } from "./config.js";
|
|
2
|
+
export declare class RetryableError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Calls the Anthropic messages API and returns the text content.
|
|
7
|
+
*
|
|
8
|
+
* Throws {@link RetryableError} for transient failures (rate limits, timeouts,
|
|
9
|
+
* 5xx errors) so callers can return `{ outcome: "fail" }` and let Camunda retry.
|
|
10
|
+
* Throws a plain Error for hard failures (auth, invalid request).
|
|
11
|
+
*/
|
|
12
|
+
export declare function callLlm(systemPrompt: string, userMessage: string, config: AiWorkerConfig): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Parses an LLM response as JSON. Handles responses wrapped in a markdown
|
|
15
|
+
* code fence (```json ... ```) which models sometimes emit.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseJsonResponse<T>(text: string): T;
|
|
18
|
+
//# sourceMappingURL=llm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,qBAAa,cAAe,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI3B;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC5B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,cAAc,GACpB,OAAO,CAAC,MAAM,CAAC,CAiCjB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAMpD"}
|
package/dist/llm.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
export class RetryableError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "RetryableError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Calls the Anthropic messages API and returns the text content.
|
|
10
|
+
*
|
|
11
|
+
* Throws {@link RetryableError} for transient failures (rate limits, timeouts,
|
|
12
|
+
* 5xx errors) so callers can return `{ outcome: "fail" }` and let Camunda retry.
|
|
13
|
+
* Throws a plain Error for hard failures (auth, invalid request).
|
|
14
|
+
*/
|
|
15
|
+
export async function callLlm(systemPrompt, userMessage, config) {
|
|
16
|
+
const client = new Anthropic({
|
|
17
|
+
apiKey: config.apiKey,
|
|
18
|
+
timeout: config.timeoutMs,
|
|
19
|
+
maxRetries: 0, // we let Camunda handle retries via failJob
|
|
20
|
+
});
|
|
21
|
+
let message;
|
|
22
|
+
try {
|
|
23
|
+
message = await client.messages.create({
|
|
24
|
+
model: config.model,
|
|
25
|
+
max_tokens: config.maxTokens,
|
|
26
|
+
system: systemPrompt,
|
|
27
|
+
messages: [{ role: "user", content: userMessage }],
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (err instanceof Anthropic.RateLimitError ||
|
|
32
|
+
err instanceof Anthropic.APIConnectionTimeoutError) {
|
|
33
|
+
throw new RetryableError(err.message);
|
|
34
|
+
}
|
|
35
|
+
if (err instanceof Anthropic.InternalServerError) {
|
|
36
|
+
throw new RetryableError(err.message);
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
const block = message.content[0];
|
|
41
|
+
if (!block || block.type !== "text") {
|
|
42
|
+
throw new Error("Unexpected response shape from Anthropic API");
|
|
43
|
+
}
|
|
44
|
+
return block.text;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parses an LLM response as JSON. Handles responses wrapped in a markdown
|
|
48
|
+
* code fence (```json ... ```) which models sometimes emit.
|
|
49
|
+
*/
|
|
50
|
+
export function parseJsonResponse(text) {
|
|
51
|
+
const stripped = text
|
|
52
|
+
.replace(/^```(?:json)?\s*/i, "")
|
|
53
|
+
.replace(/\s*```\s*$/, "")
|
|
54
|
+
.trim();
|
|
55
|
+
return JSON.parse(stripped);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=llm.js.map
|
package/dist/llm.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAA;AAGzC,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxC,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC7B,CAAC;CACD;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC5B,YAAoB,EACpB,WAAmB,EACnB,MAAsB;IAEtB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC5B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,SAAS;QACzB,UAAU,EAAE,CAAC,EAAE,4CAA4C;KAC3D,CAAC,CAAA;IAEF,IAAI,OAA0B,CAAA;IAC9B,IAAI,CAAC;QACJ,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACtC,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,UAAU,EAAE,MAAM,CAAC,SAAS;YAC5B,MAAM,EAAE,YAAY;YACpB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;SAClD,CAAC,CAAA;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IACC,GAAG,YAAY,SAAS,CAAC,cAAc;YACvC,GAAG,YAAY,SAAS,CAAC,yBAAyB,EACjD,CAAC;YACF,MAAM,IAAI,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACtC,CAAC;QACD,IAAI,GAAG,YAAY,SAAS,CAAC,mBAAmB,EAAE,CAAC;YAClD,MAAM,IAAI,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACtC,CAAC;QACD,MAAM,GAAG,CAAA;IACV,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAChC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;IAChE,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAA;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAI,IAAY;IAChD,MAAM,QAAQ,GAAG,IAAI;SACnB,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;SACzB,IAAI,EAAE,CAAA;IACR,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAM,CAAA;AACjC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WorkerJob, WorkerJobResult } from "@bpmnkit/cli-sdk";
|
|
2
|
+
import type { AiWorkerConfig } from "../config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Classifies job.variables.input into one of job.variables.categories.
|
|
5
|
+
*
|
|
6
|
+
* Required job variables:
|
|
7
|
+
* - input: string — the text to classify
|
|
8
|
+
* - categories: string[] — allowed category names
|
|
9
|
+
*
|
|
10
|
+
* Optional:
|
|
11
|
+
* - context: string — additional domain context for the model
|
|
12
|
+
*
|
|
13
|
+
* Output variables: category, confidence, rationale, aiModel, processedAt
|
|
14
|
+
*/
|
|
15
|
+
export declare function classify(job: WorkerJob, config: AiWorkerConfig): Promise<WorkerJobResult>;
|
|
16
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/operations/classify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAelD;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAwD/F"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { RetryableError, callLlm, parseJsonResponse } from "../llm.js";
|
|
2
|
+
const SYSTEM_PROMPT = `You are a text classification assistant.
|
|
3
|
+
The user will provide text to classify and a list of allowed categories.
|
|
4
|
+
Respond ONLY with valid JSON matching this exact shape:
|
|
5
|
+
{ "category": "<one of the allowed categories>", "confidence": <0.0–1.0>, "rationale": "<one sentence>" }
|
|
6
|
+
Do not include any prose outside the JSON object.`;
|
|
7
|
+
/**
|
|
8
|
+
* Classifies job.variables.input into one of job.variables.categories.
|
|
9
|
+
*
|
|
10
|
+
* Required job variables:
|
|
11
|
+
* - input: string — the text to classify
|
|
12
|
+
* - categories: string[] — allowed category names
|
|
13
|
+
*
|
|
14
|
+
* Optional:
|
|
15
|
+
* - context: string — additional domain context for the model
|
|
16
|
+
*
|
|
17
|
+
* Output variables: category, confidence, rationale, aiModel, processedAt
|
|
18
|
+
*/
|
|
19
|
+
export async function classify(job, config) {
|
|
20
|
+
const input = String(job.variables.input ?? "");
|
|
21
|
+
const categories = job.variables.categories;
|
|
22
|
+
if (!Array.isArray(categories) || categories.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
outcome: "error",
|
|
25
|
+
errorCode: "AI_INVALID_INPUT",
|
|
26
|
+
errorMessage: 'Job variable "categories" must be a non-empty array of strings.',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const context = job.variables.context ? `\nContext: ${String(job.variables.context)}` : "";
|
|
30
|
+
const userMessage = `Allowed categories: ${JSON.stringify(categories)}${context}\n\nText to classify:\n${input}`;
|
|
31
|
+
let raw;
|
|
32
|
+
try {
|
|
33
|
+
raw = await callLlm(SYSTEM_PROMPT, userMessage, config);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err instanceof RetryableError) {
|
|
37
|
+
return { outcome: "fail", errorMessage: err.message, retries: 2, retryBackOff: 30_000 };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
outcome: "error",
|
|
41
|
+
errorCode: "AI_API_ERROR",
|
|
42
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = parseJsonResponse(raw);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return {
|
|
51
|
+
outcome: "error",
|
|
52
|
+
errorCode: "AI_PARSE_ERROR",
|
|
53
|
+
errorMessage: `Model returned non-JSON response: ${raw.slice(0, 200)}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (!categories.includes(parsed.category)) {
|
|
57
|
+
return {
|
|
58
|
+
outcome: "error",
|
|
59
|
+
errorCode: "AI_INVALID_CATEGORY",
|
|
60
|
+
errorMessage: `Model returned category "${parsed.category}" which is not in the allowed list: ${JSON.stringify(categories)}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
outcome: "complete",
|
|
65
|
+
variables: {
|
|
66
|
+
category: parsed.category,
|
|
67
|
+
confidence: parsed.confidence,
|
|
68
|
+
rationale: parsed.rationale,
|
|
69
|
+
aiModel: config.model,
|
|
70
|
+
processedAt: new Date().toISOString(),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/operations/classify.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAQtE,MAAM,aAAa,GAAG;;;;kDAI4B,CAAA;AAElD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAc,EAAE,MAAsB;IACpE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IAC/C,MAAM,UAAU,GAAG,GAAG,CAAC,SAAS,CAAC,UAAU,CAAA;IAC3C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,kBAAkB;YAC7B,YAAY,EAAE,iEAAiE;SAC/E,CAAA;IACF,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAC1F,MAAM,WAAW,GAAG,uBAAuB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,OAAO,0BAA0B,KAAK,EAAE,CAAA;IAEhH,IAAI,GAAW,CAAA;IACf,IAAI,CAAC;QACJ,GAAG,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,WAAW,EAAE,MAAM,CAAC,CAAA;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAA;QACxF,CAAC;QACD,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,cAAc;YACzB,YAAY,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SAC9D,CAAA;IACF,CAAC;IAED,IAAI,MAAwB,CAAA;IAC5B,IAAI,CAAC;QACJ,MAAM,GAAG,iBAAiB,CAAmB,GAAG,CAAC,CAAA;IAClD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,gBAAgB;YAC3B,YAAY,EAAE,qCAAqC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;SACtE,CAAA;IACF,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3C,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,qBAAqB;YAChC,YAAY,EAAE,4BAA4B,MAAM,CAAC,QAAQ,uCAAuC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE;SAC5H,CAAA;IACF,CAAC;IAED,OAAO;QACN,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE;YACV,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,OAAO,EAAE,MAAM,CAAC,KAAK;YACrB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC;KACD,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.test.d.ts","sourceRoot":"","sources":["../../src/operations/classify.test.ts"],"names":[],"mappings":""}
|