ocpipe 0.2.1 → 0.3.1
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/DESIGN.md +257 -0
- package/GETTING_STARTED.md +384 -0
- package/README.md +37 -38
- package/example/ckpt/hello-world_20251227_044217.json +27 -0
- package/example/correction.ts +21 -7
- package/example/index.ts +1 -1
- package/llms.txt +200 -0
- package/package.json +18 -2
- package/src/agent.ts +46 -21
- package/src/index.ts +2 -2
- package/src/module.ts +21 -11
- package/src/parsing.ts +273 -76
- package/src/pipeline.ts +4 -3
- package/src/predict.ts +62 -24
- package/src/signature.ts +1 -1
- package/src/state.ts +1 -1
- package/src/testing.ts +23 -14
- package/src/types.ts +19 -11
package/src/predict.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ocpipe Predict class.
|
|
3
3
|
*
|
|
4
4
|
* Executes a signature by generating a prompt, calling OpenCode, and parsing the response.
|
|
5
5
|
*/
|
|
@@ -47,8 +47,13 @@ export interface PredictConfig {
|
|
|
47
47
|
correction?: CorrectionConfig | false
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
type AnySignature = SignatureDef<
|
|
51
|
+
Record<string, FieldConfig>,
|
|
52
|
+
Record<string, FieldConfig>
|
|
53
|
+
>
|
|
54
|
+
|
|
50
55
|
/** Predict executes a signature by calling an LLM and parsing the response. */
|
|
51
|
-
export class Predict<S extends
|
|
56
|
+
export class Predict<S extends AnySignature> {
|
|
52
57
|
constructor(
|
|
53
58
|
public readonly sig: S,
|
|
54
59
|
public readonly config: PredictConfig = {},
|
|
@@ -90,7 +95,11 @@ export class Predict<S extends SignatureDef<any, any>> {
|
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
// Parsing failed - attempt correction if enabled
|
|
93
|
-
if (
|
|
98
|
+
if (
|
|
99
|
+
this.config.correction !== false &&
|
|
100
|
+
parseResult.errors &&
|
|
101
|
+
parseResult.json
|
|
102
|
+
) {
|
|
94
103
|
const corrected = await this.correctFields(
|
|
95
104
|
parseResult.json,
|
|
96
105
|
parseResult.errors,
|
|
@@ -111,9 +120,19 @@ export class Predict<S extends SignatureDef<any, any>> {
|
|
|
111
120
|
|
|
112
121
|
// Correction failed or disabled - throw SchemaValidationError (non-retryable)
|
|
113
122
|
const errors = parseResult.errors ?? []
|
|
114
|
-
const errorMessages =
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
const errorMessages =
|
|
124
|
+
errors.map((e) => `${e.path}: ${e.message}`).join('; ') || 'Unknown error'
|
|
125
|
+
const correctionAttempts =
|
|
126
|
+
this.config.correction !== false ?
|
|
127
|
+
typeof this.config.correction === 'object' ?
|
|
128
|
+
(this.config.correction.maxRounds ?? 3)
|
|
129
|
+
: 3
|
|
130
|
+
: 0
|
|
131
|
+
throw new SchemaValidationError(
|
|
132
|
+
`Schema validation failed: ${errorMessages}`,
|
|
133
|
+
errors,
|
|
134
|
+
correctionAttempts,
|
|
135
|
+
)
|
|
117
136
|
}
|
|
118
137
|
|
|
119
138
|
/** correctFields attempts to fix field errors using same-session patches with retries. */
|
|
@@ -123,32 +142,39 @@ export class Predict<S extends SignatureDef<any, any>> {
|
|
|
123
142
|
ctx: ExecutionContext,
|
|
124
143
|
sessionId: string,
|
|
125
144
|
): Promise<InferOutputs<S> | null> {
|
|
126
|
-
const correctionConfig =
|
|
145
|
+
const correctionConfig =
|
|
146
|
+
typeof this.config.correction === 'object' ? this.config.correction : {}
|
|
127
147
|
const method: CorrectionMethod = correctionConfig.method ?? 'json-patch'
|
|
128
148
|
const maxFields = correctionConfig.maxFields ?? 5
|
|
129
149
|
const maxRounds = correctionConfig.maxRounds ?? 3
|
|
130
150
|
const correctionModel = correctionConfig.model
|
|
131
151
|
|
|
132
|
-
let currentJson = JSON.parse(JSON.stringify(json)) as Record<
|
|
152
|
+
let currentJson = JSON.parse(JSON.stringify(json)) as Record<
|
|
153
|
+
string,
|
|
154
|
+
unknown
|
|
155
|
+
>
|
|
133
156
|
let currentErrors = initialErrors
|
|
134
157
|
|
|
135
158
|
for (let round = 1; round <= maxRounds; round++) {
|
|
136
159
|
const errorsToFix = currentErrors.slice(0, maxFields)
|
|
137
|
-
|
|
160
|
+
|
|
138
161
|
if (errorsToFix.length === 0) {
|
|
139
162
|
break
|
|
140
163
|
}
|
|
141
164
|
|
|
142
|
-
console.error(
|
|
165
|
+
console.error(
|
|
166
|
+
`\n>>> Correction round ${round}/${maxRounds} [${method}]: fixing ${errorsToFix.length} field(s)...`,
|
|
167
|
+
)
|
|
143
168
|
|
|
144
169
|
// Build prompt based on correction method
|
|
145
|
-
const patchPrompt =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
170
|
+
const patchPrompt =
|
|
171
|
+
method === 'jq' ?
|
|
172
|
+
errorsToFix.length === 1 ?
|
|
173
|
+
buildPatchPrompt(errorsToFix[0]!, currentJson, this.sig.outputs)
|
|
174
|
+
: buildBatchPatchPrompt(errorsToFix, currentJson)
|
|
175
|
+
: errorsToFix.length === 1 ?
|
|
176
|
+
buildJsonPatchPrompt(errorsToFix[0]!, currentJson, this.sig.outputs)
|
|
177
|
+
: buildBatchJsonPatchPrompt(errorsToFix, currentJson)
|
|
152
178
|
|
|
153
179
|
// Use same session (model has context) unless correction model specified
|
|
154
180
|
const patchResult = await runAgent({
|
|
@@ -183,14 +209,16 @@ export class Predict<S extends SignatureDef<any, any>> {
|
|
|
183
209
|
|
|
184
210
|
// Update errors for next round
|
|
185
211
|
currentErrors = revalidated.errors ?? []
|
|
186
|
-
|
|
212
|
+
|
|
187
213
|
if (currentErrors.length === 0) {
|
|
188
214
|
// No errors but also no data? Shouldn't happen, but handle gracefully
|
|
189
215
|
console.error(` Unexpected state: no errors but validation failed`)
|
|
190
216
|
break
|
|
191
217
|
}
|
|
192
218
|
|
|
193
|
-
console.error(
|
|
219
|
+
console.error(
|
|
220
|
+
` Round ${round} complete, ${currentErrors.length} error(s) remaining`,
|
|
221
|
+
)
|
|
194
222
|
}
|
|
195
223
|
|
|
196
224
|
console.error(` Schema correction failed after ${maxRounds} rounds`)
|
|
@@ -216,7 +244,7 @@ export class Predict<S extends SignatureDef<any, any>> {
|
|
|
216
244
|
|
|
217
245
|
// Input fields as JSON
|
|
218
246
|
const inputsWithDescriptions: Record<string, unknown> = {}
|
|
219
|
-
for (const [name
|
|
247
|
+
for (const [name] of Object.entries(this.sig.inputs) as [
|
|
220
248
|
string,
|
|
221
249
|
FieldConfig,
|
|
222
250
|
][]) {
|
|
@@ -231,7 +259,9 @@ export class Predict<S extends SignatureDef<any, any>> {
|
|
|
231
259
|
// Output format with JSON Schema
|
|
232
260
|
lines.push('OUTPUT FORMAT:')
|
|
233
261
|
lines.push('Return a JSON object matching this schema EXACTLY.')
|
|
234
|
-
lines.push(
|
|
262
|
+
lines.push(
|
|
263
|
+
'IMPORTANT: For optional fields, OMIT the field entirely - do NOT use null.',
|
|
264
|
+
)
|
|
235
265
|
lines.push('')
|
|
236
266
|
lines.push('```json')
|
|
237
267
|
lines.push(JSON.stringify(this.buildOutputJsonSchema(), null, 2))
|
|
@@ -244,7 +274,10 @@ export class Predict<S extends SignatureDef<any, any>> {
|
|
|
244
274
|
private buildOutputJsonSchema(): Record<string, unknown> {
|
|
245
275
|
// Build a Zod object from the output fields
|
|
246
276
|
const shape: Record<string, z.ZodType> = {}
|
|
247
|
-
for (const [name, config] of Object.entries(this.sig.outputs) as [
|
|
277
|
+
for (const [name, config] of Object.entries(this.sig.outputs) as [
|
|
278
|
+
string,
|
|
279
|
+
FieldConfig,
|
|
280
|
+
][]) {
|
|
248
281
|
shape[name] = config.type
|
|
249
282
|
}
|
|
250
283
|
const outputSchema = z.object(shape)
|
|
@@ -254,9 +287,14 @@ export class Predict<S extends SignatureDef<any, any>> {
|
|
|
254
287
|
|
|
255
288
|
// Add field descriptions from our config (toJSONSchema uses .describe() metadata)
|
|
256
289
|
// Since our FieldConfig has a separate desc field, merge it in
|
|
257
|
-
const props = jsonSchema.properties as
|
|
290
|
+
const props = jsonSchema.properties as
|
|
291
|
+
| Record<string, Record<string, unknown>>
|
|
292
|
+
| undefined
|
|
258
293
|
if (props) {
|
|
259
|
-
for (const [name, config] of Object.entries(this.sig.outputs) as [
|
|
294
|
+
for (const [name, config] of Object.entries(this.sig.outputs) as [
|
|
295
|
+
string,
|
|
296
|
+
FieldConfig,
|
|
297
|
+
][]) {
|
|
260
298
|
if (config.desc && props[name]) {
|
|
261
299
|
// Only add if not already set by .describe()
|
|
262
300
|
if (!props[name].description) {
|
package/src/signature.ts
CHANGED
package/src/state.ts
CHANGED
package/src/testing.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ocpipe testing utilities.
|
|
3
3
|
*
|
|
4
|
-
* Provides mock backends and test helpers for unit testing
|
|
4
|
+
* Provides mock backends and test helpers for unit testing ocpipe components.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { RunAgentOptions, RunAgentResult, FieldConfig } from './types.js'
|
|
@@ -34,7 +34,10 @@ export class MockAgentBackend {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/** addJsonResponse adds a mock JSON response. */
|
|
37
|
-
addJsonResponse(
|
|
37
|
+
addJsonResponse(
|
|
38
|
+
data: Record<string, unknown>,
|
|
39
|
+
options?: Partial<MockResponse>,
|
|
40
|
+
): this {
|
|
38
41
|
return this.addResponse({
|
|
39
42
|
response: JSON.stringify(data, null, 2),
|
|
40
43
|
...options,
|
|
@@ -76,7 +79,7 @@ export class MockAgentBackend {
|
|
|
76
79
|
for (let i = 0; i < this.responses.length; i++) {
|
|
77
80
|
const r = this.responses[i]
|
|
78
81
|
if (!r) continue
|
|
79
|
-
|
|
82
|
+
|
|
80
83
|
if (!r.match) {
|
|
81
84
|
response = r
|
|
82
85
|
responseIndex = i
|
|
@@ -116,7 +119,8 @@ export class MockAgentBackend {
|
|
|
116
119
|
|
|
117
120
|
return {
|
|
118
121
|
text: response.response ?? '',
|
|
119
|
-
sessionId:
|
|
122
|
+
sessionId:
|
|
123
|
+
response.sessionId ?? options.sessionId ?? this.defaultSessionId,
|
|
120
124
|
}
|
|
121
125
|
}
|
|
122
126
|
|
|
@@ -127,12 +131,14 @@ export class MockAgentBackend {
|
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
/** createMockContext creates a test execution context. */
|
|
130
|
-
export function createMockContext(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
export function createMockContext(
|
|
135
|
+
overrides?: Partial<{
|
|
136
|
+
sessionId: string
|
|
137
|
+
defaultModel: { providerID: string; modelID: string }
|
|
138
|
+
defaultAgent: string
|
|
139
|
+
timeoutSec: number
|
|
140
|
+
}>,
|
|
141
|
+
) {
|
|
136
142
|
return {
|
|
137
143
|
sessionId: overrides?.sessionId,
|
|
138
144
|
defaultModel: overrides?.defaultModel ?? {
|
|
@@ -145,12 +151,14 @@ export function createMockContext(overrides?: Partial<{
|
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
/** generateMockOutputs creates mock output data based on a schema. */
|
|
148
|
-
export function generateMockOutputs(
|
|
154
|
+
export function generateMockOutputs(
|
|
155
|
+
schema: Record<string, FieldConfig>,
|
|
156
|
+
): Record<string, unknown> {
|
|
149
157
|
const result: Record<string, unknown> = {}
|
|
150
158
|
for (const [name, config] of Object.entries(schema)) {
|
|
151
159
|
// Use constructor name for type detection (works across zod versions)
|
|
152
160
|
const typeName = config.type.constructor.name
|
|
153
|
-
|
|
161
|
+
|
|
154
162
|
switch (typeName) {
|
|
155
163
|
case 'ZodString':
|
|
156
164
|
result[name] = `mock_${name}`
|
|
@@ -167,11 +175,12 @@ export function generateMockOutputs(schema: Record<string, FieldConfig>): Record
|
|
|
167
175
|
case 'ZodObject':
|
|
168
176
|
result[name] = {}
|
|
169
177
|
break
|
|
170
|
-
case 'ZodEnum':
|
|
178
|
+
case 'ZodEnum': {
|
|
171
179
|
// Get first enum value via options property
|
|
172
180
|
const enumType = config.type as { options?: readonly string[] }
|
|
173
181
|
result[name] = enumType.options?.[0] ?? 'unknown'
|
|
174
182
|
break
|
|
183
|
+
}
|
|
175
184
|
default:
|
|
176
185
|
result[name] = null
|
|
177
186
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Core type definitions for the Declarative Self-Improving TypeScript SDK.
|
|
2
|
+
* ocpipe shared types.
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
5
|
import type { z } from 'zod/v4'
|
|
@@ -131,16 +129,26 @@ export interface SignatureDef<
|
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
/** Infer the input type from a signature definition. */
|
|
134
|
-
export type InferInputs<
|
|
135
|
-
S extends SignatureDef<
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
export type InferInputs<
|
|
133
|
+
S extends SignatureDef<
|
|
134
|
+
Record<string, FieldConfig>,
|
|
135
|
+
Record<string, FieldConfig>
|
|
136
|
+
>,
|
|
137
|
+
> =
|
|
138
|
+
S extends SignatureDef<infer I, Record<string, FieldConfig>> ?
|
|
139
|
+
{ [K in keyof I]: z.infer<I[K]['type']> }
|
|
140
|
+
: never
|
|
138
141
|
|
|
139
142
|
/** Infer the output type from a signature definition. */
|
|
140
|
-
export type InferOutputs<
|
|
141
|
-
S extends SignatureDef<
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
export type InferOutputs<
|
|
144
|
+
S extends SignatureDef<
|
|
145
|
+
Record<string, FieldConfig>,
|
|
146
|
+
Record<string, FieldConfig>
|
|
147
|
+
>,
|
|
148
|
+
> =
|
|
149
|
+
S extends SignatureDef<Record<string, FieldConfig>, infer O> ?
|
|
150
|
+
{ [K in keyof O]: z.infer<O[K]['type']> }
|
|
151
|
+
: never
|
|
144
152
|
|
|
145
153
|
// ============================================================================
|
|
146
154
|
// Retry Configuration
|