@openapi-flow/langchain 0.3.14 → 0.3.16
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 +14 -0
- package/dist/arguments/analyze-api-input-answers.js +118 -84
- package/dist/arguments/generate-api-arguments.js +105 -81
- package/dist/bindings/plan-api-bindings-by-call.d.ts +4 -0
- package/dist/bindings/plan-api-bindings-by-call.js +77 -0
- package/dist/bindings/plan-api-bindings.js +1 -0
- package/dist/bindings/plan-api-call-bindings.d.ts +3 -0
- package/dist/bindings/plan-api-call-bindings.js +82 -0
- package/dist/bindings/select-api-binding-sources.d.ts +3 -0
- package/dist/bindings/select-api-binding-sources.js +55 -0
- package/dist/model/invoke-structured-model.js +2 -2
- package/dist/prompts/api-call-bindings-prompt.d.ts +2 -0
- package/dist/prompts/api-call-bindings-prompt.js +8 -0
- package/dist/prompts/incremental-workflow-connections-prompt.d.ts +2 -0
- package/dist/prompts/incremental-workflow-connections-prompt.js +10 -0
- package/dist/public-api.d.ts +8 -0
- package/dist/public-api.js +6 -0
- package/dist/schemas/api-call-binding-plan-schema.d.ts +7 -0
- package/dist/schemas/api-call-binding-plan-schema.js +46 -0
- package/dist/schemas/connection-planning-field-schemas.d.ts +8 -0
- package/dist/schemas/connection-planning-field-schemas.js +27 -0
- package/dist/schemas/workflow-connection-goals-schema.d.ts +4 -0
- package/dist/schemas/workflow-connection-goals-schema.js +53 -0
- package/dist/schemas/workflow-connection-step-schema.d.ts +4 -0
- package/dist/schemas/workflow-connection-step-schema.js +69 -0
- package/dist/selection/select-api-operations.js +1 -0
- package/dist/types/api-call-binding-planning.d.ts +17 -0
- package/dist/types/api-call-binding-planning.js +1 -0
- package/dist/types/incremental-connection-planning.d.ts +60 -0
- package/dist/types/incremental-connection-planning.js +1 -0
- package/dist/types/native-node-planning.d.ts +3 -0
- package/dist/types/structured-model.d.ts +4 -1
- package/dist/workflow/connections/invoke-connection-planning-model.d.ts +3 -0
- package/dist/workflow/connections/invoke-connection-planning-model.js +35 -0
- package/dist/workflow/connections/plan-workflow-connection-goals.d.ts +4 -0
- package/dist/workflow/connections/plan-workflow-connection-goals.js +55 -0
- package/dist/workflow/connections/plan-workflow-connection-step.d.ts +2 -0
- package/dist/workflow/connections/plan-workflow-connection-step.js +98 -0
- package/dist/workflow/connections/resolve-connection-port-selection.d.ts +4 -0
- package/dist/workflow/connections/resolve-connection-port-selection.js +15 -0
- package/dist/workflow/connections/workflow-connection-planning-error.d.ts +8 -0
- package/dist/workflow/connections/workflow-connection-planning-error.js +26 -0
- package/dist/workflow/generate-native-node-parameters.js +3 -1
- package/dist/workflow/plan-native-instance-dependencies.js +3 -1
- package/dist/workflow/select-native-node-instances.js +3 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @openapi-flow/langchain
|
|
2
2
|
|
|
3
|
+
Version **0.3.16** adds `planWorkflowConnectionGoals` and
|
|
4
|
+
`planWorkflowConnectionStep`. Each makes one structured model call; the host owns
|
|
5
|
+
looping, checkpointing and review. Native planning stages accept optional abort
|
|
6
|
+
signals. See the [connection guide](https://github.com/carefreelife98/openapi-flow/blob/main/docs/incremental-connection-planning.md)
|
|
7
|
+
and [release status and limitations](https://github.com/carefreelife98/openapi-flow/blob/main/docs/releases/0.3.16.md).
|
|
8
|
+
|
|
9
|
+
Version **0.3.15** adds `selectApiBindingSources`, `planApiCallBindings` and
|
|
10
|
+
`planApiBindingsByCall` for scoped API binding work. The model no longer needs to
|
|
11
|
+
regenerate a whole selected-call inventory in this path. Code checks complete
|
|
12
|
+
coverage and cross-call cycles, and the host explicitly chooses concurrency and
|
|
13
|
+
stores validated progress for resume. Existing `planApiBindings` stays compatible.
|
|
14
|
+
See [scalable bindings](https://github.com/carefreelife98/openapi-flow/blob/main/docs/scalable-api-binding-planning.md)
|
|
15
|
+
and the [release record](https://github.com/carefreelife98/openapi-flow/blob/main/docs/releases/0.3.15.md).
|
|
16
|
+
|
|
3
17
|
Version **0.3.14** adds source-owned structured errors and preserves initial
|
|
4
18
|
argument provenance failures across optional enum selection. Existing prompts,
|
|
5
19
|
values, schemas and retry behavior are unchanged; the host still chooses the
|
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
2
|
-
import { captureApiInputAnswerContext, createApiInputAnswerExtractionRecord, resolveApiInputAnswerInterpretations, describeApiInputEnumAnswerChoices, describeApiInputSources, resolveApiInputSourceSelections, } from '@openapi-flow/core';
|
|
2
|
+
import { captureApiInputAnswerContext, createApiInputAnswerExtractionRecord, resolveApiInputAnswerInterpretations, describeApiInputEnumAnswerChoices, describeApiInputSources, resolveApiInputSourceSelections, runOpenApiFlowOperation, } from '@openapi-flow/core';
|
|
3
3
|
import { createApiInputAnswerSourceSchema, apiInputAnswerExplanationOptionsSchema, } from '../schemas/api-input-answer-source-schema.js';
|
|
4
4
|
import { apiInputAnswersPrompt } from '../prompts/api-input-answers-prompt.js';
|
|
5
5
|
import { serializeStructuredOutputSchema } from '../schemas/serialize-structured-output-schema.js';
|
|
6
6
|
import { parseStructuredOutput } from '../legacy/planning/parse-structured-output.js';
|
|
7
|
+
import { invokeStructuredModel } from '../model/invoke-structured-model.js';
|
|
7
8
|
export async function analyzeApiInputAnswers(input) {
|
|
8
|
-
const options =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
const { options, snapshot } = runOpenApiFlowOperation({
|
|
10
|
+
owner: 'langchain',
|
|
11
|
+
operation: 'analyzeApiInputAnswers',
|
|
12
|
+
stage: 'input-validation',
|
|
13
|
+
code: 'PLANNING_INPUT_INVALID',
|
|
14
|
+
}, () => {
|
|
15
|
+
const options = apiInputAnswerExplanationOptionsSchema.parse({
|
|
16
|
+
includeExplanations: input.includeExplanations,
|
|
17
|
+
selectOasEnums: input.selectOasEnums,
|
|
18
|
+
});
|
|
19
|
+
const snapshot = captureApiInputAnswerContext({
|
|
20
|
+
reply: input.reply,
|
|
21
|
+
requirements: input.requirements,
|
|
22
|
+
...(input.purpose === undefined ? {} : { purpose: input.purpose }),
|
|
23
|
+
...(options.selectOasEnums === undefined
|
|
24
|
+
? {}
|
|
25
|
+
: { selectOasEnums: options.selectOasEnums }),
|
|
26
|
+
});
|
|
27
|
+
if (input.model !== undefined &&
|
|
28
|
+
(!input.model || typeof input.model.withStructuredOutput !== 'function'))
|
|
29
|
+
throw new Error('analyzeApiInputAnswers.model must support structured output when supplied');
|
|
30
|
+
return { options, snapshot };
|
|
19
31
|
});
|
|
20
|
-
if (input.model !== undefined &&
|
|
21
|
-
(!input.model || typeof input.model.withStructuredOutput !== 'function'))
|
|
22
|
-
throw new Error('analyzeApiInputAnswers.model must support structured output when supplied');
|
|
23
32
|
if (!snapshot.requirements.length || snapshot.reply === '')
|
|
24
33
|
return {
|
|
25
34
|
status: snapshot.requirements.length
|
|
@@ -37,82 +46,107 @@ export async function analyzeApiInputAnswers(input) {
|
|
|
37
46
|
sources: [],
|
|
38
47
|
unansweredRequirementIds: snapshot.requirements.map((item) => item.id),
|
|
39
48
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
const { model, schema, wireSchema, enumChoices } = runOpenApiFlowOperation({
|
|
50
|
+
owner: 'langchain',
|
|
51
|
+
operation: 'analyzeApiInputAnswers',
|
|
52
|
+
stage: 'input-validation',
|
|
53
|
+
code: 'PLANNING_INPUT_INVALID',
|
|
54
|
+
}, () => {
|
|
55
|
+
if (!input.model)
|
|
56
|
+
throw new Error('analyzeApiInputAnswers.model is required for a nonempty reply with pending inputs');
|
|
57
|
+
const enumChoices = describeApiInputEnumAnswerChoices(snapshot);
|
|
58
|
+
const schema = createApiInputAnswerSourceSchema(snapshot.requirements, catalog, options.includeExplanations === true, enumChoices);
|
|
59
|
+
return {
|
|
60
|
+
model: input.model,
|
|
61
|
+
schema,
|
|
62
|
+
enumChoices,
|
|
63
|
+
wireSchema: serializeStructuredOutputSchema(schema, 'draft-2020-12'),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
const output = await invokeStructuredModel({
|
|
67
|
+
model,
|
|
68
|
+
schema: wireSchema,
|
|
69
|
+
operation: 'analyzeApiInputAnswers',
|
|
46
70
|
name: 'extract_api_input_answers',
|
|
47
71
|
method: 'functionCalling',
|
|
48
72
|
strict: true,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
]);
|
|
62
|
-
const parsed = parseStructuredOutput(schema, output, 'model clarification answer sources');
|
|
63
|
-
const selections = resolveApiInputSourceSelections({
|
|
64
|
-
...snapshot,
|
|
65
|
-
selections: parsed.answers.flatMap(({ requirementId, source }) => source.kind === 'oas-enum' ? [] : [{ requirementId, source }]),
|
|
73
|
+
messages: [
|
|
74
|
+
new SystemMessage(apiInputAnswersPrompt),
|
|
75
|
+
new HumanMessage(JSON.stringify({
|
|
76
|
+
reply: snapshot.reply,
|
|
77
|
+
questions: snapshot.requirements,
|
|
78
|
+
...(snapshot.purpose === undefined
|
|
79
|
+
? {}
|
|
80
|
+
: { purpose: snapshot.purpose }),
|
|
81
|
+
sources: catalog,
|
|
82
|
+
...(enumChoices.length ? { enumChoices } : {}),
|
|
83
|
+
})),
|
|
84
|
+
],
|
|
66
85
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
return runOpenApiFlowOperation({
|
|
87
|
+
owner: 'langchain',
|
|
88
|
+
operation: 'analyzeApiInputAnswers',
|
|
89
|
+
stage: 'contract-validation',
|
|
90
|
+
code: 'API_ARGUMENT_VALIDATION_FAILED',
|
|
91
|
+
}, () => {
|
|
92
|
+
const parsed = parseStructuredOutput(schema, output, 'model clarification answer sources');
|
|
93
|
+
const selections = resolveApiInputSourceSelections({
|
|
73
94
|
...snapshot,
|
|
74
|
-
selections: [
|
|
75
|
-
{ requirementId: answer.requirementId, source: answer.source.intent },
|
|
76
|
-
],
|
|
95
|
+
selections: parsed.answers.flatMap(({ requirementId, source }) => source.kind === 'oas-enum' ? [] : [{ requirementId, source }]),
|
|
77
96
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
const enumSelections = parsed.answers.flatMap((answer) => {
|
|
98
|
+
if (answer.source.kind !== 'oas-enum')
|
|
99
|
+
return [];
|
|
100
|
+
if (typeof answer.reason !== 'string')
|
|
101
|
+
throw new Error(`Enum answer ${answer.requirementId} requires an explanation`);
|
|
102
|
+
const [intent] = resolveApiInputSourceSelections({
|
|
103
|
+
...snapshot,
|
|
104
|
+
selections: [
|
|
105
|
+
{
|
|
106
|
+
requirementId: answer.requirementId,
|
|
107
|
+
source: answer.source.intent,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
requirementId: answer.requirementId,
|
|
114
|
+
candidateId: answer.source.candidateId,
|
|
115
|
+
intent: {
|
|
116
|
+
sourceFormat: intent.sourceFormat,
|
|
117
|
+
sourceText: intent.sourceText,
|
|
118
|
+
},
|
|
119
|
+
reason: answer.reason,
|
|
85
120
|
},
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
const resolved = resolveApiInputAnswerInterpretations({
|
|
91
|
-
...snapshot,
|
|
92
|
-
selections,
|
|
93
|
-
...(enumSelections.length ? { enumSelections } : {}),
|
|
94
|
-
});
|
|
95
|
-
let extraction;
|
|
96
|
-
if (options.includeExplanations === true) {
|
|
97
|
-
if (typeof parsed.reason !== 'string')
|
|
98
|
-
throw new Error('model clarification answer sources require an explanation');
|
|
99
|
-
extraction = createApiInputAnswerExtractionRecord({
|
|
121
|
+
];
|
|
122
|
+
});
|
|
123
|
+
const resolved = resolveApiInputAnswerInterpretations({
|
|
100
124
|
...snapshot,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
answers: selections.map((selection) => {
|
|
104
|
-
const source = parsed.answers.find((answer) => answer.requirementId === selection.requirementId);
|
|
105
|
-
if (typeof source?.reason !== 'string')
|
|
106
|
-
throw new Error(`model answer ${selection.requirementId} requires an explanation`);
|
|
107
|
-
return { ...selection, reason: source.reason };
|
|
108
|
-
}),
|
|
109
|
-
...(enumSelections.length ? { enumSelections } : {}),
|
|
110
|
-
},
|
|
125
|
+
selections,
|
|
126
|
+
...(enumSelections.length ? { enumSelections } : {}),
|
|
111
127
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
128
|
+
let extraction;
|
|
129
|
+
if (options.includeExplanations === true) {
|
|
130
|
+
if (typeof parsed.reason !== 'string')
|
|
131
|
+
throw new Error('model clarification answer sources require an explanation');
|
|
132
|
+
extraction = createApiInputAnswerExtractionRecord({
|
|
133
|
+
...snapshot,
|
|
134
|
+
decision: {
|
|
135
|
+
reason: parsed.reason,
|
|
136
|
+
answers: selections.map((selection) => {
|
|
137
|
+
const source = parsed.answers.find((answer) => answer.requirementId === selection.requirementId);
|
|
138
|
+
if (typeof source?.reason !== 'string')
|
|
139
|
+
throw new Error(`model answer ${selection.requirementId} requires an explanation`);
|
|
140
|
+
return { ...selection, reason: source.reason };
|
|
141
|
+
}),
|
|
142
|
+
...(enumSelections.length ? { enumSelections } : {}),
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
status: 'extracted',
|
|
148
|
+
...resolved,
|
|
149
|
+
...(extraction === undefined ? {} : { extraction }),
|
|
150
|
+
};
|
|
151
|
+
});
|
|
118
152
|
}
|
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
import { SystemMessage, HumanMessage } from '@langchain/core/messages';
|
|
2
|
-
import { describeApiLiteralGeneration, createApiLiteralGenerationRecord, applyConfirmedApiInputs, createApiRequestSchema, inspectApiArgumentRequirements, resolveApiLiteralProposalSources, } from '@openapi-flow/core';
|
|
2
|
+
import { describeApiLiteralGeneration, createApiLiteralGenerationRecord, applyConfirmedApiInputs, createApiRequestSchema, inspectApiArgumentRequirements, resolveApiLiteralProposalSources, runOpenApiFlowOperation, runOpenApiFlowAsyncOperation, } from '@openapi-flow/core';
|
|
3
3
|
import { apiLiteralGenerationOptionsSchema, createApiLiteralGenerationOutputSchema, } from '../schemas/api-literal-generation-output-schema.js';
|
|
4
4
|
import { apiArgumentsPrompt } from '../prompts/api-arguments-prompt.js';
|
|
5
5
|
import { parseStructuredOutput } from '../legacy/planning/parse-structured-output.js';
|
|
6
6
|
import { interpretApiArgumentEnums } from './interpret-api-argument-enums.js';
|
|
7
|
+
import { invokeStructuredModel } from '../model/invoke-structured-model.js';
|
|
7
8
|
export async function generateApiArguments(input) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const { options, snapshot, contract, context, trackInputs, schema } = runOpenApiFlowOperation({
|
|
10
|
+
owner: 'langchain',
|
|
11
|
+
operation: 'generateApiArguments',
|
|
12
|
+
stage: 'input-validation',
|
|
13
|
+
code: 'PLANNING_INPUT_INVALID',
|
|
14
|
+
}, () => {
|
|
15
|
+
if (input.model !== undefined &&
|
|
16
|
+
(!input.model ||
|
|
17
|
+
typeof input.model.withStructuredOutput !== 'function'))
|
|
18
|
+
throw new Error('generateApiArguments.model must support structured output when supplied');
|
|
19
|
+
if (typeof input.callId !== 'string' || !input.callId.trim())
|
|
20
|
+
throw new Error('callId must be non-empty');
|
|
21
|
+
const options = apiLiteralGenerationOptionsSchema.parse({
|
|
22
|
+
includeExplanations: input.includeExplanations,
|
|
23
|
+
selectOasEnums: input.selectOasEnums,
|
|
24
|
+
trackRequestedInputs: input.trackRequestedInputs,
|
|
25
|
+
});
|
|
26
|
+
const { input: snapshot, contract, context, } = describeApiLiteralGeneration(input);
|
|
27
|
+
const trackInputs = options.trackRequestedInputs === true ||
|
|
28
|
+
options.selectOasEnums === true;
|
|
29
|
+
const schema = createApiLiteralGenerationOutputSchema(contract, options.includeExplanations === true, trackInputs ? createApiRequestSchema(snapshot) : undefined);
|
|
30
|
+
if (contract.hasLiteralInputs && !input.model)
|
|
31
|
+
throw new Error('generateApiArguments.model must support structured output when literal inputs remain');
|
|
32
|
+
return { options, snapshot, contract, context, trackInputs, schema };
|
|
17
33
|
});
|
|
18
|
-
const { input: snapshot, contract, context, } = describeApiLiteralGeneration(input);
|
|
19
|
-
const trackInputs = options.trackRequestedInputs === true || options.selectOasEnums === true;
|
|
20
|
-
const schema = createApiLiteralGenerationOutputSchema(contract, options.includeExplanations === true, trackInputs ? createApiRequestSchema(snapshot) : undefined);
|
|
21
34
|
let proposed = {
|
|
22
35
|
values: {},
|
|
23
36
|
...(trackInputs ? { requestedInputs: [], literalSources: [] } : {}),
|
|
@@ -26,78 +39,89 @@ export async function generateApiArguments(input) {
|
|
|
26
39
|
const model = input.model;
|
|
27
40
|
if (!model || typeof model.withStructuredOutput !== 'function')
|
|
28
41
|
throw new Error('generateApiArguments.model must support structured output when literal inputs remain');
|
|
29
|
-
proposed = await
|
|
30
|
-
|
|
42
|
+
proposed = await invokeStructuredModel({
|
|
43
|
+
model,
|
|
44
|
+
schema,
|
|
45
|
+
operation: 'generateApiArguments',
|
|
31
46
|
name: 'generate_api_arguments',
|
|
32
47
|
method: 'functionCalling',
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
// Preserve the existing provider-default strictness of literal generation.
|
|
49
|
+
strict: undefined,
|
|
50
|
+
messages: [
|
|
51
|
+
new SystemMessage(apiArgumentsPrompt),
|
|
52
|
+
new HumanMessage(JSON.stringify({
|
|
53
|
+
...context,
|
|
54
|
+
...(options.selectOasEnums === true
|
|
55
|
+
? { selectOasEnums: true }
|
|
56
|
+
: {}),
|
|
57
|
+
})),
|
|
58
|
+
],
|
|
59
|
+
});
|
|
43
60
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
return runOpenApiFlowAsyncOperation({
|
|
62
|
+
owner: 'langchain',
|
|
63
|
+
operation: 'generateApiArguments',
|
|
64
|
+
stage: 'contract-validation',
|
|
65
|
+
code: 'API_ARGUMENT_VALIDATION_FAILED',
|
|
66
|
+
}, async () => {
|
|
67
|
+
const parsed = parseStructuredOutput(schema, proposed, `model arguments for ${snapshot.callId}`);
|
|
68
|
+
const values = applyConfirmedApiInputs({
|
|
69
|
+
...snapshot,
|
|
53
70
|
values: parsed.values,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
values,
|
|
69
|
-
bindings: structuredClone(snapshot.bindings),
|
|
70
|
-
unresolvedInputs: inspection.requirements.map((item) => item.targetPointer),
|
|
71
|
-
...(literalSources === undefined ? {} : { literalSources }),
|
|
72
|
-
...(requestedInputs === undefined ? {} : { requestedInputs }),
|
|
73
|
-
...(snapshot.requestMediaType === undefined
|
|
74
|
-
? {}
|
|
75
|
-
: { requestMediaType: snapshot.requestMediaType }),
|
|
76
|
-
...(options.includeExplanations === true
|
|
77
|
-
? {
|
|
78
|
-
literalGeneration: createApiLiteralGenerationRecord({
|
|
79
|
-
...snapshot,
|
|
80
|
-
...('explanation' in parsed
|
|
81
|
-
? {
|
|
82
|
-
proposal: { values: parsed.values },
|
|
83
|
-
reason: parsed.explanation.reason,
|
|
84
|
-
}
|
|
85
|
-
: {}),
|
|
86
|
-
}),
|
|
87
|
-
}
|
|
88
|
-
: {}),
|
|
89
|
-
};
|
|
90
|
-
if (options.selectOasEnums === true) {
|
|
91
|
-
if (literalSources === undefined)
|
|
92
|
-
throw new Error('generateApiArguments enum interpretation requires source-verified literalSources');
|
|
93
|
-
return interpretApiArgumentEnums({
|
|
71
|
+
});
|
|
72
|
+
const requestedInputs = 'requestedInputs' in parsed ? parsed.requestedInputs : undefined;
|
|
73
|
+
const literalSources = 'literalSources' in parsed
|
|
74
|
+
? resolveApiLiteralProposalSources({
|
|
75
|
+
callId: snapshot.callId,
|
|
76
|
+
values: parsed.values,
|
|
77
|
+
sources: parsed.literalSources.map((source) => ({
|
|
78
|
+
...source,
|
|
79
|
+
messageId: 'scenario',
|
|
80
|
+
})),
|
|
81
|
+
messages: [{ id: 'scenario', text: snapshot.scenario }],
|
|
82
|
+
})
|
|
83
|
+
: undefined;
|
|
84
|
+
const inspection = inspectApiArgumentRequirements({
|
|
94
85
|
...snapshot,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
proposal: { values: parsed.values },
|
|
98
|
-
literalSources,
|
|
99
|
-
model: input.model,
|
|
86
|
+
values,
|
|
87
|
+
requestedInputs,
|
|
100
88
|
});
|
|
101
|
-
|
|
102
|
-
|
|
89
|
+
const result = {
|
|
90
|
+
callId: snapshot.callId,
|
|
91
|
+
values,
|
|
92
|
+
bindings: structuredClone(snapshot.bindings),
|
|
93
|
+
unresolvedInputs: inspection.requirements.map((item) => item.targetPointer),
|
|
94
|
+
...(literalSources === undefined ? {} : { literalSources }),
|
|
95
|
+
...(requestedInputs === undefined ? {} : { requestedInputs }),
|
|
96
|
+
...(snapshot.requestMediaType === undefined
|
|
97
|
+
? {}
|
|
98
|
+
: { requestMediaType: snapshot.requestMediaType }),
|
|
99
|
+
...(options.includeExplanations === true
|
|
100
|
+
? {
|
|
101
|
+
literalGeneration: createApiLiteralGenerationRecord({
|
|
102
|
+
...snapshot,
|
|
103
|
+
...('explanation' in parsed
|
|
104
|
+
? {
|
|
105
|
+
proposal: { values: parsed.values },
|
|
106
|
+
reason: parsed.explanation.reason,
|
|
107
|
+
}
|
|
108
|
+
: {}),
|
|
109
|
+
}),
|
|
110
|
+
}
|
|
111
|
+
: {}),
|
|
112
|
+
};
|
|
113
|
+
if (options.selectOasEnums === true) {
|
|
114
|
+
if (literalSources === undefined)
|
|
115
|
+
throw new Error('generateApiArguments enum interpretation requires source-verified literalSources');
|
|
116
|
+
return interpretApiArgumentEnums({
|
|
117
|
+
...snapshot,
|
|
118
|
+
...options,
|
|
119
|
+
arguments: result,
|
|
120
|
+
proposal: { values: parsed.values },
|
|
121
|
+
literalSources,
|
|
122
|
+
model: input.model,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
});
|
|
103
127
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ApiBindingPlan } from '@openapi-flow/core';
|
|
2
|
+
import type { PlanApiBindingsByCallInput } from '../types/api-call-binding-planning.js';
|
|
3
|
+
/** Resource-bounded scheduling; each result can also be checkpointed by a host graph. */
|
|
4
|
+
export declare function planApiBindingsByCall(input: PlanApiBindingsByCallInput): Promise<ApiBindingPlan>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describeApiBindingPlanning, validateApiCallBindingPlan, assembleApiBindingPlan, runOpenApiFlowOperation, } from '@openapi-flow/core';
|
|
2
|
+
import { apiBindingCallExecutionSchema } from '../schemas/api-call-binding-plan-schema.js';
|
|
3
|
+
import { selectApiBindingSources } from './select-api-binding-sources.js';
|
|
4
|
+
import { planApiCallBindings } from './plan-api-call-bindings.js';
|
|
5
|
+
/** Resource-bounded scheduling; each result can also be checkpointed by a host graph. */
|
|
6
|
+
export async function planApiBindingsByCall(input) {
|
|
7
|
+
const { snapshot, concurrency, completed } = runOpenApiFlowOperation({
|
|
8
|
+
owner: 'langchain',
|
|
9
|
+
operation: 'planApiBindingsByCall',
|
|
10
|
+
stage: 'input-validation',
|
|
11
|
+
code: 'PLANNING_INPUT_INVALID',
|
|
12
|
+
}, () => {
|
|
13
|
+
const { concurrency } = apiBindingCallExecutionSchema.parse(input);
|
|
14
|
+
if (input.onCallPlanned !== undefined &&
|
|
15
|
+
typeof input.onCallPlanned !== 'function')
|
|
16
|
+
throw new Error('onCallPlanned must be a function when supplied');
|
|
17
|
+
const snapshot = describeApiBindingPlanning(input).input;
|
|
18
|
+
const completed = input.completedCalls === undefined
|
|
19
|
+
? []
|
|
20
|
+
: structuredClone(input.completedCalls);
|
|
21
|
+
if (!Array.isArray(completed))
|
|
22
|
+
throw new Error('completedCalls must be an array');
|
|
23
|
+
if (new Set(completed.map((item) => item.callId)).size !== completed.length)
|
|
24
|
+
throw new Error('completedCalls contains duplicate API call identities');
|
|
25
|
+
for (const result of completed)
|
|
26
|
+
validateApiCallBindingPlan({
|
|
27
|
+
...snapshot,
|
|
28
|
+
callId: result.callId,
|
|
29
|
+
includeExplanations: input.includeExplanations,
|
|
30
|
+
result,
|
|
31
|
+
});
|
|
32
|
+
return { snapshot, concurrency, completed };
|
|
33
|
+
});
|
|
34
|
+
input.signal?.throwIfAborted();
|
|
35
|
+
const results = new Map(completed.map((result) => [result.callId, result]));
|
|
36
|
+
const pending = snapshot.materials.filter((material) => !results.has(material.callId));
|
|
37
|
+
let next = 0;
|
|
38
|
+
let failed = false;
|
|
39
|
+
let failure;
|
|
40
|
+
async function worker() {
|
|
41
|
+
while (!failed && next < pending.length) {
|
|
42
|
+
const material = pending[next++];
|
|
43
|
+
try {
|
|
44
|
+
input.signal?.throwIfAborted();
|
|
45
|
+
const callInput = {
|
|
46
|
+
...snapshot,
|
|
47
|
+
callId: material.callId,
|
|
48
|
+
includeExplanations: input.includeExplanations,
|
|
49
|
+
model: input.model,
|
|
50
|
+
signal: input.signal,
|
|
51
|
+
};
|
|
52
|
+
const sources = await selectApiBindingSources(callInput);
|
|
53
|
+
if (failed)
|
|
54
|
+
return;
|
|
55
|
+
const result = await planApiCallBindings({ ...callInput, sources });
|
|
56
|
+
input.signal?.throwIfAborted();
|
|
57
|
+
if (input.onCallPlanned !== undefined)
|
|
58
|
+
await input.onCallPlanned(structuredClone(result));
|
|
59
|
+
results.set(result.callId, result);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (!failed)
|
|
63
|
+
failure = error;
|
|
64
|
+
failed = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, pending.length) }, () => worker()));
|
|
69
|
+
if (failed)
|
|
70
|
+
throw failure;
|
|
71
|
+
input.signal?.throwIfAborted();
|
|
72
|
+
return assembleApiBindingPlan({
|
|
73
|
+
...snapshot,
|
|
74
|
+
results: [...results.values()],
|
|
75
|
+
includeExplanations: input.includeExplanations,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
3
|
+
import { describeApiCallBindingPlanning, resolveApiBindingSourceSelection, resolveApiCallBindingPlan, listApiResponsePointers, runOpenApiFlowOperation, } from '@openapi-flow/core';
|
|
4
|
+
import { createApiCallBindingPlanSchema } from '../schemas/api-call-binding-plan-schema.js';
|
|
5
|
+
import { apiCallBindingsPrompt } from '../prompts/api-call-bindings-prompt.js';
|
|
6
|
+
import { invokeStructuredModel } from '../model/invoke-structured-model.js';
|
|
7
|
+
import { parseStructuredOutput } from '../legacy/planning/parse-structured-output.js';
|
|
8
|
+
export async function planApiCallBindings(input) {
|
|
9
|
+
const { description, sources } = runOpenApiFlowOperation({
|
|
10
|
+
owner: 'langchain',
|
|
11
|
+
operation: 'planApiCallBindings',
|
|
12
|
+
stage: 'input-validation',
|
|
13
|
+
code: 'PLANNING_INPUT_INVALID',
|
|
14
|
+
}, () => {
|
|
15
|
+
const description = describeApiCallBindingPlanning(input);
|
|
16
|
+
const sources = structuredClone(input.sources);
|
|
17
|
+
const expected = resolveApiBindingSourceSelection({
|
|
18
|
+
...description.input,
|
|
19
|
+
proposal: {
|
|
20
|
+
sourceNodeIds: sources.sourceNodeIds,
|
|
21
|
+
gaps: sources.gaps,
|
|
22
|
+
...(sources.reason === undefined ? {} : { reason: sources.reason }),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
if (!isDeepStrictEqual(sources, expected))
|
|
26
|
+
throw new Error('binding source selection changed or belongs to another call or source snapshot');
|
|
27
|
+
return { description, sources };
|
|
28
|
+
});
|
|
29
|
+
input.signal?.throwIfAborted();
|
|
30
|
+
if (!sources.sourceNodeIds.length)
|
|
31
|
+
return resolveApiCallBindingPlan({
|
|
32
|
+
...description.input,
|
|
33
|
+
sources,
|
|
34
|
+
proposal: {
|
|
35
|
+
bindings: [],
|
|
36
|
+
gaps: [],
|
|
37
|
+
...(sources.reason === undefined ? {} : { reason: sources.reason }),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const selected = new Set(sources.sourceNodeIds);
|
|
41
|
+
const schema = createApiCallBindingPlanSchema(description, sources.sourceNodeIds);
|
|
42
|
+
const output = await invokeStructuredModel({
|
|
43
|
+
model: input.model,
|
|
44
|
+
schema,
|
|
45
|
+
operation: 'planApiCallBindings',
|
|
46
|
+
name: 'plan_api_call_bindings',
|
|
47
|
+
method: 'functionCalling',
|
|
48
|
+
strict: true,
|
|
49
|
+
signal: input.signal,
|
|
50
|
+
messages: [
|
|
51
|
+
new SystemMessage(apiCallBindingsPrompt),
|
|
52
|
+
new HumanMessage(JSON.stringify({
|
|
53
|
+
scenario: description.input.scenario,
|
|
54
|
+
target: description.target,
|
|
55
|
+
requestSchema: description.requestSchema,
|
|
56
|
+
apiSources: description.input.materials
|
|
57
|
+
.filter((material) => selected.has(material.callId))
|
|
58
|
+
.map((material) => ({
|
|
59
|
+
nodeId: material.callId,
|
|
60
|
+
key: material.operation.key,
|
|
61
|
+
method: material.operation.method,
|
|
62
|
+
path: material.operation.path,
|
|
63
|
+
purpose: material.purpose,
|
|
64
|
+
responses: material.operation.operation.responses,
|
|
65
|
+
responsePointerCatalog: listApiResponsePointers(material.operation),
|
|
66
|
+
})),
|
|
67
|
+
nativeSources: description.input.nativeOutputs?.filter((source) => selected.has(source.nodeId)),
|
|
68
|
+
})),
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
input.signal?.throwIfAborted();
|
|
72
|
+
return runOpenApiFlowOperation({
|
|
73
|
+
owner: 'langchain',
|
|
74
|
+
operation: 'planApiCallBindings',
|
|
75
|
+
stage: 'contract-validation',
|
|
76
|
+
code: 'API_BINDING_VALIDATION_FAILED',
|
|
77
|
+
}, () => resolveApiCallBindingPlan({
|
|
78
|
+
...description.input,
|
|
79
|
+
sources,
|
|
80
|
+
proposal: parseStructuredOutput(schema, output, 'API call bindings'),
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ApiBindingSourceSelection } from '@openapi-flow/core';
|
|
2
|
+
import type { SelectApiBindingSourcesInput } from '../types/api-call-binding-planning.js';
|
|
3
|
+
export declare function selectApiBindingSources(input: SelectApiBindingSourcesInput): Promise<ApiBindingSourceSelection>;
|