mdi-llmkit 1.0.1 → 1.0.6

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/README.md CHANGED
@@ -1,190 +1,170 @@
1
- # mdi-llmkit (TypeScript)
2
-
3
- Utilities for managing LLM chat conversations and structured JSON responses with OpenAI's Responses API.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install mdi-llmkit openai
9
- ```
10
-
11
- ## Quick Start
12
-
13
- ### `gptSubmit`
14
-
15
- ```ts
16
- import OpenAI from 'openai';
17
- import { gptSubmit } from 'mdi-llmkit';
18
-
19
- const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
20
-
21
- const reply = await gptSubmit(
22
- [{ role: 'user', content: 'Say hello.' }],
23
- client
24
- );
25
-
26
- console.log(reply);
27
- ```
28
-
29
- ### `GptConversation`
30
-
31
- ```ts
32
- import OpenAI from 'openai';
33
- import { GptConversation } from 'mdi-llmkit';
34
-
35
- const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
36
- const conversation = new GptConversation([], { openaiClient: client });
37
-
38
- const reply = await conversation.submitUserMessage(
39
- 'Give me three project name ideas.'
40
- );
41
- console.log(reply);
42
- ```
43
-
44
- ### `JSONSchemaFormat`
45
-
46
- ```ts
47
- import { JSONSchemaFormat, JSON_INTEGER, gptSubmit } from 'mdi-llmkit';
48
-
49
- const responseFormat = JSONSchemaFormat(
50
- 'answer_payload',
51
- {
52
- answer: 'The final answer',
53
- confidence: ['Confidence score', [0, 100], []],
54
- rank: JSON_INTEGER,
55
- },
56
- 'Structured answer payload'
57
- );
58
-
59
- const result = await gptSubmit(
60
- [{ role: 'user', content: 'Return answer as structured JSON.' }],
61
- client,
62
- { jsonResponse: responseFormat }
63
- );
64
- ```
65
-
66
- ## `jsonSurgery`
67
-
68
- `jsonSurgery` applies iterative, model-guided edits to a JSON-compatible object using
69
- structured JSON-path operations (`assign`, `append`, `insert`, `delete`, `rename`).
70
-
71
- ```ts
72
- import { jsonSurgery } from 'mdi-llmkit/jsonSurgery';
73
- ```
74
-
75
- - It deep-copies the input object and returns the modified copy.
76
- - It supports optional schema guidance and key-skipping for model-visible context.
77
- - It supports validation/progress callbacks and soft iteration/time limits.
78
-
79
- ## `compareItemLists` (comparison)
80
-
81
- `compareItemLists` performs a semantic diff between a "before" list and an "after" list,
82
- including LLM-assisted rename/add/remove decisions.
83
-
84
- Types:
85
-
86
- - `SemanticallyComparableListItem`
87
- - `string`
88
- - `{ name: string; description?: string }`
89
- - `ItemComparisonResult`
90
- - `Removed | Added | Renamed | Unchanged`
91
- - `OnComparingItemCallback`
92
- - `(item, isFromBeforeList, isStarting, result, newName, error, totalProcessedSoFar, totalLeftToProcess) => void`
93
-
94
- Behavior notes:
95
-
96
- - Item matching is name-based and case-insensitive.
97
- - `description` provides extra model context but is not identity.
98
- - Names are expected to be unique within each list (case-insensitive).
99
- - Progress callback is fired at item start (`isStarting=true`) and finish (`isStarting=false`).
100
-
101
- Example:
102
-
103
- ```ts
104
- import OpenAI from 'openai';
105
- import {
106
- compareItemLists,
107
- ItemComparisonResult,
108
- type OnComparingItemCallback,
109
- } from 'mdi-llmkit/comparison';
110
-
111
- const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
112
-
113
- const onComparingItem: OnComparingItemCallback = (
114
- item,
115
- isFromBeforeList,
116
- isStarting,
117
- result,
118
- newName,
119
- error,
120
- processed,
121
- left
122
- ) => {
123
- if (error) {
124
- console.warn('Comparison warning:', error);
125
- }
126
- if (!isStarting && result === ItemComparisonResult.Renamed) {
127
- console.log('Renamed:', item, '->', newName);
128
- }
129
- console.log({ isFromBeforeList, isStarting, result, processed, left });
130
- };
131
-
132
- const comparison = await compareItemLists(
133
- client,
134
- [{ name: 'Widget A', description: 'Legacy widget' }, 'Widget B'],
135
- [
136
- { name: 'Widget Alpha', description: 'Migrated name for Widget A' },
137
- 'Widget B',
138
- ],
139
- 'Widgets migrated from legacy catalog to new naming standards.',
140
- onComparingItem
141
- );
142
-
143
- console.log(comparison);
144
- ```
145
-
146
- ## JSON Response Mode
147
-
148
- ```ts
149
- import OpenAI from 'openai';
150
- import { gptSubmit } from 'mdi-llmkit';
151
-
152
- const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
153
-
154
- const result = await gptSubmit(
155
- [{ role: 'user', content: 'Return JSON with keys a and b.' }],
156
- client,
157
- { jsonResponse: true }
158
- );
159
-
160
- console.log(result);
161
- ```
162
-
163
- ## Notes
164
-
165
- - Current TypeScript parity slices include `gptSubmit`, `GptConversation`, and `JSONSchemaFormat`.
166
- - You can import GPT API symbols via subpath imports, e.g. `import { GptConversation } from "mdi-llmkit/gptApi"`.
167
- - Comparison symbols are available via `mdi-llmkit/comparison`.
168
- - Integer schemas can be expressed with `JSON_INTEGER`; numeric (float-capable) schemas can use `JSON_NUMBER`.
169
-
170
- ## Migration from Python
171
-
172
- - Function naming: Python `gpt_submit(...)` maps to TypeScript `gptSubmit(...)`.
173
- - Argument style: Python keyword args map to a TypeScript options object.
174
- - Conversation submit methods: Python `submit_user_message(...)` maps to `submitUserMessage(...)`.
175
- - JSON schema DSL: Python tuple metadata uses TypeScript array metadata.
176
- - Python: `("Age", (0, 120), int)`
177
- - TypeScript: `["Age", [0, 120], JSON_INTEGER]`
178
- - JSON schema type markers in TypeScript:
179
- - `JSON_INTEGER` for integer-only values.
180
- - `JSON_NUMBER` for float-capable numeric values.
181
-
182
- ## CI and Release
183
-
184
- - CI workflow: `.github/workflows/typescript-ci.yml`
185
- - Runs on push to `main` and on pull requests when TypeScript package files change.
186
- - Executes `npm ci`, `npm test`, and `npm run build` in `packages/typescript-mdi-llmkit`.
187
- - Release workflow: `.github/workflows/typescript-release.yml`
188
- - Runs on tags matching `typescript-v*` (for example: `typescript-v1.0.1`).
189
- - Requires repository secret `NPM_TOKEN` with publish permission to npm.
190
- - Executes tests/build before `npm publish --access public --provenance`.
1
+ # mdi-llmkit (TypeScript)
2
+
3
+ Utilities for managing LLM chat conversations and structured JSON responses with OpenAI's Responses API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install mdi-llmkit openai
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### `gptSubmit`
14
+
15
+ ```ts
16
+ import OpenAI from 'openai';
17
+ import { gptSubmit } from 'mdi-llmkit';
18
+
19
+ const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
20
+
21
+ const reply = await gptSubmit(
22
+ [{ role: 'user', content: 'Say hello.' }],
23
+ client
24
+ );
25
+
26
+ console.log(reply);
27
+ ```
28
+
29
+ ### `GptConversation`
30
+
31
+ ```ts
32
+ import OpenAI from 'openai';
33
+ import { GptConversation } from 'mdi-llmkit';
34
+
35
+ const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
36
+ const conversation = new GptConversation([], { openaiClient: client });
37
+
38
+ const reply = await conversation.submitUserMessage(
39
+ 'Give me three project name ideas.'
40
+ );
41
+ console.log(reply);
42
+ ```
43
+
44
+ ### `JSONSchemaFormat`
45
+
46
+ ```ts
47
+ import { JSONSchemaFormat, JSON_INTEGER, gptSubmit } from 'mdi-llmkit';
48
+
49
+ const responseFormat = JSONSchemaFormat(
50
+ 'answer_payload',
51
+ {
52
+ answer: 'The final answer',
53
+ confidence: ['Confidence score', [0, 100], []],
54
+ rank: JSON_INTEGER,
55
+ },
56
+ 'Structured answer payload'
57
+ );
58
+
59
+ const result = await gptSubmit(
60
+ [{ role: 'user', content: 'Return answer as structured JSON.' }],
61
+ client,
62
+ { jsonResponse: responseFormat }
63
+ );
64
+ ```
65
+
66
+ ## `jsonSurgery`
67
+
68
+ `jsonSurgery` applies iterative, model-guided edits to a JSON-compatible object using
69
+ structured JSON-path operations (`assign`, `append`, `insert`, `delete`, `rename`).
70
+
71
+ ```ts
72
+ import { jsonSurgery } from 'mdi-llmkit/jsonSurgery';
73
+ ```
74
+
75
+ - It deep-copies the input object and returns the modified copy.
76
+ - It supports optional schema guidance and key-skipping for model-visible context.
77
+ - It supports validation/progress callbacks and soft iteration/time limits.
78
+
79
+ ## `compareItemLists` (comparison)
80
+
81
+ `compareItemLists` performs a semantic diff between a "before" list and an "after" list,
82
+ including LLM-assisted rename/add/remove decisions.
83
+
84
+ Types:
85
+
86
+ - `SemanticallyComparableListItem`
87
+ - `string`
88
+ - `{ name: string; description?: string }`
89
+ - `ItemComparisonResult`
90
+ - `Removed | Added | Renamed | Unchanged`
91
+ - `OnComparingItemCallback`
92
+ - `(item, isFromBeforeList, isStarting, result, newName, error, totalProcessedSoFar, totalLeftToProcess) => void`
93
+
94
+ Behavior notes:
95
+
96
+ - Item matching is name-based and case-insensitive.
97
+ - `description` provides extra model context but is not identity.
98
+ - Names are expected to be unique within each list (case-insensitive).
99
+ - Progress callback is fired at item start (`isStarting=true`) and finish (`isStarting=false`).
100
+
101
+ Example:
102
+
103
+ ```ts
104
+ import OpenAI from 'openai';
105
+ import {
106
+ compareItemLists,
107
+ ItemComparisonResult,
108
+ type OnComparingItemCallback,
109
+ } from 'mdi-llmkit/comparison';
110
+
111
+ const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
112
+
113
+ const onComparingItem: OnComparingItemCallback = (
114
+ item,
115
+ isFromBeforeList,
116
+ isStarting,
117
+ result,
118
+ newName,
119
+ error,
120
+ processed,
121
+ left
122
+ ) => {
123
+ if (error) {
124
+ console.warn('Comparison warning:', error);
125
+ }
126
+ if (!isStarting && result === ItemComparisonResult.Renamed) {
127
+ console.log('Renamed:', item, '->', newName);
128
+ }
129
+ console.log({ isFromBeforeList, isStarting, result, processed, left });
130
+ };
131
+
132
+ const comparison = await compareItemLists(
133
+ client,
134
+ [{ name: 'Widget A', description: 'Legacy widget' }, 'Widget B'],
135
+ [
136
+ { name: 'Widget Alpha', description: 'Migrated name for Widget A' },
137
+ 'Widget B',
138
+ ],
139
+ 'Widgets migrated from legacy catalog to new naming standards.',
140
+ onComparingItem
141
+ );
142
+
143
+ console.log(comparison);
144
+ ```
145
+
146
+ ## JSON Response Mode
147
+
148
+ ```ts
149
+ import OpenAI from 'openai';
150
+ import { gptSubmit } from 'mdi-llmkit';
151
+
152
+ const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
153
+
154
+ const result = await gptSubmit(
155
+ [{ role: 'user', content: 'Return JSON with keys a and b.' }],
156
+ client,
157
+ { jsonResponse: true }
158
+ );
159
+
160
+ console.log(result);
161
+ ```
162
+
163
+
164
+ ## CI and Release
165
+
166
+ - Unified CI + release workflow: `.github/workflows/typescript-release.yml`
167
+ - Runs CI on pull requests and on pushes to `main` when TypeScript package files change.
168
+ - Executes `npm ci`, `npm test`, and `npm run build` in `packages/typescript-mdi-llmkit`.
169
+ - On push to `main`, publishes to npm only if `package.json` version changed and that version is not already published.
170
+ - Uses repository secret `NPM_TOKEN` for npm authentication.
@@ -157,35 +157,35 @@ export const compareItemLists = async (openaiClient, listBefore, listAfter, expl
157
157
  // However, some of these items may be renames rather than pure additions/removals.
158
158
  // The only way to tell is with AI.
159
159
  const convo = new GptConversation([], { openaiClient });
160
- convo.addSystemMessage(`
161
- You are a data analyst who has been hired to try to preserve the integrity of a list of
162
- data items that have recently undergone migration from one data system to another.
163
-
164
- You will be given two lists of items: a "before" list and an "after" list.
165
- (The exact nature of the items is not important. They could be names of products from
166
- receipts or purchase orders, for example.)
167
-
168
- In the migration from the old data system to the new, some items may have been removed,
169
- some items may have been added, and some items may have been renamed. We can't tell
170
- just by performing string comparisons on the two lists, because the renames may be subtle.
171
-
172
- We're going to go through the items in the "before" list, one by one. For each one,
173
- you will look for the best matching item in the "after" list. If you find a good match,
174
- you will consider that item to be a rename of the original item. If you don't find a
175
- good match, you will consider that item to have been removed.
160
+ convo.addSystemMessage(`
161
+ You are a data analyst who has been hired to try to preserve the integrity of a list of
162
+ data items that have recently undergone migration from one data system to another.
163
+
164
+ You will be given two lists of items: a "before" list and an "after" list.
165
+ (The exact nature of the items is not important. They could be names of products from
166
+ receipts or purchase orders, for example.)
167
+
168
+ In the migration from the old data system to the new, some items may have been removed,
169
+ some items may have been added, and some items may have been renamed. We can't tell
170
+ just by performing string comparisons on the two lists, because the renames may be subtle.
171
+
172
+ We're going to go through the items in the "before" list, one by one. For each one,
173
+ you will look for the best matching item in the "after" list. If you find a good match,
174
+ you will consider that item to be a rename of the original item. If you don't find a
175
+ good match, you will consider that item to have been removed.
176
176
  `);
177
177
  if (explanation) {
178
- convo.addSystemMessage(`
179
- Here is some additional context that may help you make better decisions about which items
180
- have been renamed versus removed/added:
181
-
182
- ${explanation}
178
+ convo.addSystemMessage(`
179
+ Here is some additional context that may help you make better decisions about which items
180
+ have been renamed versus removed/added:
181
+
182
+ ${explanation}
183
183
  `);
184
184
  }
185
- convo.addUserMessage(`
186
- "BEFORE" LIST:
187
-
188
- ${listBefore.map(itemToPromptString).join('\n')}
185
+ convo.addUserMessage(`
186
+ "BEFORE" LIST:
187
+
188
+ ${listBefore.map(itemToPromptString).join('\n')}
189
189
  `);
190
190
  // Counts used for onComparingItem telemetry across both loops.
191
191
  let totalProcessedItems = 0;
@@ -198,30 +198,30 @@ ${listBefore.map(itemToPromptString).join('\n')}
198
198
  const convoIter = convo.clone();
199
199
  // We rebuild the "after" list each time, since items may get removed from it
200
200
  // as they get matched.
201
- convoIter.addUserMessage(`
202
- "AFTER" LIST:
203
-
204
- ${listAfter.map(itemToPromptString).join('\n')}
201
+ convoIter.addUserMessage(`
202
+ "AFTER" LIST:
203
+
204
+ ${listAfter.map(itemToPromptString).join('\n')}
205
205
  `);
206
- convoIter.addUserMessage(`
207
- For the moment, let's focus on this item from the "before" list:
208
-
209
- ${itemToPromptString(itemBefore)}
210
-
211
- Look through the entire "after" list and try to find an item that might be a rename
212
- or alternative version of this item.
213
-
214
- Feel free to think aloud, brainstorm, and reason through the possibilities. Later on,
215
- I'll ask you to formalize your decision in JSON format; but for now, just explore the options.
216
-
217
- If you find an item that seems like a good match, tell us what it is.
218
- !IMPORTANT: You may only pick *one* item from the "after" list as a potential rename of this item.
219
-
220
- If you don't find any good match, simply say that no good match was found. In this situation,
221
- we'll consider this item as having been removed/deleted.
222
-
223
- Naturally, if you have any higher-level instructions or context that apply to this item,
224
- please take them into account as you reason through the possibilities.
206
+ convoIter.addUserMessage(`
207
+ For the moment, let's focus on this item from the "before" list:
208
+
209
+ ${itemToPromptString(itemBefore)}
210
+
211
+ Look through the entire "after" list and try to find an item that might be a rename
212
+ or alternative version of this item.
213
+
214
+ Feel free to think aloud, brainstorm, and reason through the possibilities. Later on,
215
+ I'll ask you to formalize your decision in JSON format; but for now, just explore the options.
216
+
217
+ If you find an item that seems like a good match, tell us what it is.
218
+ !IMPORTANT: You may only pick *one* item from the "after" list as a potential rename of this item.
219
+
220
+ If you don't find any good match, simply say that no good match was found. In this situation,
221
+ we'll consider this item as having been removed/deleted.
222
+
223
+ Naturally, if you have any higher-level instructions or context that apply to this item,
224
+ please take them into account as you reason through the possibilities.
225
225
  `);
226
226
  await convoIter.submit();
227
227
  await convoIter.submit(undefined, undefined, {
@@ -322,22 +322,22 @@ please take them into account as you reason through the possibilities.
322
322
  onComparingItem?.(itemAfter, false, true, ItemComparisonResult.Unchanged, undefined, undefined, totalProcessedItems, listAfter.length - iItem);
323
323
  try {
324
324
  const convoIter = convo.clone();
325
- convoIter.addUserMessage(`
326
- At the moment, let's focus on this item from the "after" list:
327
-
328
- ${itemToPromptString(itemAfter)}
329
-
330
- We think that this item was newly added, because we can't find any matching item
331
- from the "before" list. However, it's possible that we have instructions or context
332
- that indicate otherwise.
333
-
334
- At this point, we don't have the option of matching this item to any item from the "before"
335
- list, since we've already processed all those items. However, we still have the option
336
- of rejecting this item from addition -- in which case, it will be considered as not having
337
- been added at all (or, in other words, it will be ignored in downstream processing).
338
-
339
- What do you think? Should we consider this item as truly added, or should we reject / ignore
340
- this item?
325
+ convoIter.addUserMessage(`
326
+ At the moment, let's focus on this item from the "after" list:
327
+
328
+ ${itemToPromptString(itemAfter)}
329
+
330
+ We think that this item was newly added, because we can't find any matching item
331
+ from the "before" list. However, it's possible that we have instructions or context
332
+ that indicate otherwise.
333
+
334
+ At this point, we don't have the option of matching this item to any item from the "before"
335
+ list, since we've already processed all those items. However, we still have the option
336
+ of rejecting this item from addition -- in which case, it will be considered as not having
337
+ been added at all (or, in other words, it will be ignored in downstream processing).
338
+
339
+ What do you think? Should we consider this item as truly added, or should we reject / ignore
340
+ this item?
341
341
  `);
342
342
  await convoIter.submit();
343
343
  await convoIter.submit(undefined, undefined, {