@botbotgo/agent-harness 0.0.91 → 0.0.93
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 +138 -31
- package/README.zh.md +93 -28
- package/dist/benchmark/upstream-runtime-ab-benchmark.d.ts +1 -1
- package/dist/benchmark/upstream-runtime-ab-benchmark.js +2 -1
- package/dist/config/workflows/langgraph-workflows.yaml +292 -0
- package/dist/contracts/types.d.ts +8 -3
- package/dist/init-project.js +7 -7
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/agent-runtime-adapter.d.ts +48 -1
- package/dist/runtime/agent-runtime-adapter.js +1001 -50
- package/dist/runtime/harness.d.ts +2 -0
- package/dist/runtime/harness.js +55 -11
- package/dist/runtime/inventory.d.ts +1 -1
- package/dist/runtime/inventory.js +1 -1
- package/dist/runtime/langgraph-presets.d.ts +23 -0
- package/dist/runtime/langgraph-presets.js +165 -0
- package/dist/runtime/policy-engine.js +0 -5
- package/dist/runtime/support/compiled-binding.d.ts +4 -1
- package/dist/runtime/support/compiled-binding.js +24 -2
- package/dist/runtime/support/harness-support.js +3 -3
- package/dist/runtime/support/runtime-entry.js +1 -1
- package/dist/workspace/agent-binding-compiler.js +82 -8
- package/dist/workspace/compile.js +1 -3
- package/dist/workspace/object-loader.js +47 -6
- package/dist/workspace/support/agent-capabilities.js +2 -2
- package/dist/workspace/support/workspace-ref-utils.d.ts +2 -1
- package/dist/workspace/support/workspace-ref-utils.js +21 -0
- package/dist/workspace/validate.js +1 -1
- package/package.json +2 -2
- /package/dist/config/{backends.yaml → catalogs/backends.yaml} +0 -0
- /package/dist/config/{embedding-models.yaml → catalogs/embedding-models.yaml} +0 -0
- /package/dist/config/{mcp.yaml → catalogs/mcp.yaml} +0 -0
- /package/dist/config/{models.yaml → catalogs/models.yaml} +0 -0
- /package/dist/config/{stores.yaml → catalogs/stores.yaml} +0 -0
- /package/dist/config/{tools.yaml → catalogs/tools.yaml} +0 -0
- /package/dist/config/{vector-stores.yaml → catalogs/vector-stores.yaml} +0 -0
- /package/dist/config/{runtime-memory.yaml → runtime/runtime-memory.yaml} +0 -0
- /package/dist/config/{workspace.yaml → runtime/workspace.yaml} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import { Command, MemorySaver } from "@langchain/langgraph";
|
|
3
4
|
import { tool as createLangChainTool } from "@langchain/core/tools";
|
|
4
5
|
import { HumanMessage, ToolMessage } from "@langchain/core/messages";
|
|
@@ -16,8 +17,9 @@ import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, ex
|
|
|
16
17
|
import { wrapToolForExecution } from "./tool-hitl.js";
|
|
17
18
|
import { resolveDeclaredMiddleware } from "./declared-middleware.js";
|
|
18
19
|
import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
|
|
19
|
-
import { getBindingDeepAgentParams, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
|
|
20
|
+
import { getBindingAdapterKind, getBindingDeepAgentParams, getBindingAdapterConfig, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingLangGraphPreset, getBindingLangGraphWorkflow, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingRuntimeModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
|
|
20
21
|
import { readSkillMetadata } from "./support/skill-metadata.js";
|
|
22
|
+
import { resolveLangGraphPresetWorkflow } from "./langgraph-presets.js";
|
|
21
23
|
function countConfiguredTools(binding) {
|
|
22
24
|
return getBindingPrimaryTools(binding).length;
|
|
23
25
|
}
|
|
@@ -161,6 +163,28 @@ function hasConfiguredSubagentSupport(binding) {
|
|
|
161
163
|
}
|
|
162
164
|
return (params.subagents?.length ?? 0) > 0 || params.generalPurposeAgent === true || Boolean(params.taskDescription?.trim());
|
|
163
165
|
}
|
|
166
|
+
function isOpenAICompatibleGptOssModel(model) {
|
|
167
|
+
return model?.provider === "openai-compatible" && model.model.trim().toLowerCase().startsWith("gpt-oss");
|
|
168
|
+
}
|
|
169
|
+
export function shouldRelaxDeepAgentDelegationPrompt(model, params) {
|
|
170
|
+
if (!isOpenAICompatibleGptOssModel(model)) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if ((params.subagents?.length ?? 0) === 0) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return params.generalPurposeAgent === true || Boolean(params.taskDescription?.trim());
|
|
177
|
+
}
|
|
178
|
+
function applyDeepAgentDelegationPromptCompatibility(model, params) {
|
|
179
|
+
if (!shouldRelaxDeepAgentDelegationPrompt(model, params)) {
|
|
180
|
+
return params;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
...params,
|
|
184
|
+
generalPurposeAgent: undefined,
|
|
185
|
+
taskDescription: undefined,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
164
188
|
function hasConfiguredMiddlewareKind(binding, kind) {
|
|
165
189
|
return getBindingMiddlewareConfigs(binding)?.some((entry) => entry.kind === kind) ?? false;
|
|
166
190
|
}
|
|
@@ -437,6 +461,7 @@ export class AgentRuntimeAdapter {
|
|
|
437
461
|
options;
|
|
438
462
|
modelCache = new Map();
|
|
439
463
|
runnableCache = new WeakMap();
|
|
464
|
+
langGraphSessions = new Map();
|
|
440
465
|
constructor(options = {}) {
|
|
441
466
|
this.options = options;
|
|
442
467
|
}
|
|
@@ -1038,6 +1063,9 @@ export class AgentRuntimeAdapter {
|
|
|
1038
1063
|
}
|
|
1039
1064
|
return toolCalls.every((toolCall) => {
|
|
1040
1065
|
const resolvedToolName = resolveModelFacingToolName(toolCall.name, toolNameMapping, primaryTools);
|
|
1066
|
+
if (resolvedToolName === "task" || toolCall.name === "task") {
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1041
1069
|
const executable = executableTools.get(toolCall.name) ?? executableTools.get(resolvedToolName);
|
|
1042
1070
|
if (executable) {
|
|
1043
1071
|
return false;
|
|
@@ -1071,29 +1099,30 @@ export class AgentRuntimeAdapter {
|
|
|
1071
1099
|
if (!params) {
|
|
1072
1100
|
return [];
|
|
1073
1101
|
}
|
|
1102
|
+
const compatibleParams = applyDeepAgentDelegationPromptCompatibility(params.model, params);
|
|
1074
1103
|
const automaticMiddleware = [];
|
|
1075
1104
|
automaticMiddleware.push(createPatchToolCallsMiddleware());
|
|
1076
1105
|
automaticMiddleware.push(...(await this.resolveAutomaticSummarizationMiddleware(binding)));
|
|
1077
|
-
if ((
|
|
1106
|
+
if ((compatibleParams.skills?.length ?? 0) > 0) {
|
|
1078
1107
|
automaticMiddleware.push(createSkillsMiddleware({
|
|
1079
1108
|
backend: this.resolveFilesystemBackend(binding),
|
|
1080
|
-
sources:
|
|
1109
|
+
sources: compatibleParams.skills,
|
|
1081
1110
|
}));
|
|
1082
1111
|
}
|
|
1083
|
-
if ((
|
|
1112
|
+
if ((compatibleParams.memory?.length ?? 0) > 0) {
|
|
1084
1113
|
automaticMiddleware.push(createMemoryMiddleware({
|
|
1085
1114
|
backend: this.resolveFilesystemBackend(binding),
|
|
1086
|
-
sources:
|
|
1115
|
+
sources: compatibleParams.memory,
|
|
1087
1116
|
}));
|
|
1088
1117
|
}
|
|
1089
1118
|
if (hasConfiguredSubagentSupport(binding)) {
|
|
1090
1119
|
automaticMiddleware.push(createSubAgentMiddleware({
|
|
1091
|
-
defaultModel: (await this.resolveModel(
|
|
1092
|
-
defaultTools: this.resolveTools(
|
|
1120
|
+
defaultModel: (await this.resolveModel(compatibleParams.model)),
|
|
1121
|
+
defaultTools: this.resolveTools(compatibleParams.tools, binding),
|
|
1093
1122
|
defaultInterruptOn: getBindingInterruptCompatibilityRules(binding),
|
|
1094
|
-
subagents: (await this.resolveSubagents(
|
|
1095
|
-
generalPurposeAgent:
|
|
1096
|
-
taskDescription:
|
|
1123
|
+
subagents: (await this.resolveSubagents(compatibleParams.subagents ?? [], binding)),
|
|
1124
|
+
generalPurposeAgent: compatibleParams.generalPurposeAgent,
|
|
1125
|
+
taskDescription: compatibleParams.taskDescription ?? null,
|
|
1097
1126
|
}));
|
|
1098
1127
|
}
|
|
1099
1128
|
return automaticMiddleware;
|
|
@@ -1128,6 +1157,12 @@ export class AgentRuntimeAdapter {
|
|
|
1128
1157
|
resolveCheckpointer(binding) {
|
|
1129
1158
|
return this.options.checkpointerResolver ? this.options.checkpointerResolver(binding) : new MemorySaver();
|
|
1130
1159
|
}
|
|
1160
|
+
resolveLangGraphWorkflowCheckpointer(binding) {
|
|
1161
|
+
const checkpointer = this.resolveCheckpointer(binding);
|
|
1162
|
+
return typeof checkpointer === "object" && checkpointer
|
|
1163
|
+
? checkpointer
|
|
1164
|
+
: undefined;
|
|
1165
|
+
}
|
|
1131
1166
|
buildRouteSystemPrompt(primaryBinding, secondaryBinding, overridePrompt) {
|
|
1132
1167
|
const defaultPrompt = `You are a routing classifier for an agent harness. Reply with exactly one agent id: ${primaryBinding.agent.id} or ${secondaryBinding.agent.id}.\n\n` +
|
|
1133
1168
|
`Choose ${primaryBinding.agent.id} only for lightweight conversational turns that can be answered directly in one step ` +
|
|
@@ -1178,59 +1213,887 @@ export class AgentRuntimeAdapter {
|
|
|
1178
1213
|
})),
|
|
1179
1214
|
})));
|
|
1180
1215
|
}
|
|
1216
|
+
async createLangChainRunnable(binding, options = {}) {
|
|
1217
|
+
const params = getBindingLangChainParams(binding);
|
|
1218
|
+
const interruptOn = this.resolveInterruptOn(binding);
|
|
1219
|
+
const model = (await this.resolveModel(params.model));
|
|
1220
|
+
const tools = this.resolveTools(params.tools, binding);
|
|
1221
|
+
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
1222
|
+
throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
|
|
1223
|
+
}
|
|
1224
|
+
return createAgent({
|
|
1225
|
+
...(options.passthroughOverride ?? params.passthrough ?? {}),
|
|
1226
|
+
model: model,
|
|
1227
|
+
tools: tools,
|
|
1228
|
+
systemPrompt: options.systemPromptOverride ?? params.systemPrompt,
|
|
1229
|
+
stateSchema: params.stateSchema,
|
|
1230
|
+
responseFormat: params.responseFormat,
|
|
1231
|
+
contextSchema: params.contextSchema,
|
|
1232
|
+
middleware: (await this.resolveMiddleware(binding, interruptOn)),
|
|
1233
|
+
checkpointer: this.resolveCheckpointer(binding),
|
|
1234
|
+
store: this.options.storeResolver?.(binding),
|
|
1235
|
+
includeAgentName: params.includeAgentName,
|
|
1236
|
+
version: params.version,
|
|
1237
|
+
name: params.name,
|
|
1238
|
+
description: params.description,
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
normalizeLangGraphWorkflowNode(raw) {
|
|
1242
|
+
if (!isRecord(raw)) {
|
|
1243
|
+
return null;
|
|
1244
|
+
}
|
|
1245
|
+
const id = typeof raw.id === "string" ? raw.id.trim() : "";
|
|
1246
|
+
const kind = typeof raw.kind === "string" ? raw.kind.trim() : "";
|
|
1247
|
+
if (!id || !kind) {
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
return {
|
|
1251
|
+
id,
|
|
1252
|
+
kind,
|
|
1253
|
+
...(typeof raw.prompt === "string" && raw.prompt.trim() ? { prompt: raw.prompt.trim() } : {}),
|
|
1254
|
+
...(typeof raw.specialist === "string" && raw.specialist.trim() ? { specialist: raw.specialist.trim() } : {}),
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
listLangGraphWorkflowNodes(workflow) {
|
|
1258
|
+
return Array.isArray(workflow.nodes)
|
|
1259
|
+
? workflow.nodes.map((node) => this.normalizeLangGraphWorkflowNode(node)).filter((node) => node !== null)
|
|
1260
|
+
: [];
|
|
1261
|
+
}
|
|
1262
|
+
normalizeLangGraphWorkflowEdge(raw) {
|
|
1263
|
+
if (!isRecord(raw)) {
|
|
1264
|
+
return null;
|
|
1265
|
+
}
|
|
1266
|
+
const from = typeof raw.from === "string" ? raw.from.trim() : "";
|
|
1267
|
+
const to = typeof raw.to === "string" ? raw.to.trim() : "";
|
|
1268
|
+
if (!from || !to) {
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
from,
|
|
1273
|
+
to,
|
|
1274
|
+
...(typeof raw.when === "string" && raw.when.trim() ? { when: raw.when.trim() } : {}),
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
listLangGraphWorkflowEdges(workflow) {
|
|
1278
|
+
if (!Array.isArray(workflow.edges)) {
|
|
1279
|
+
return [];
|
|
1280
|
+
}
|
|
1281
|
+
return workflow.edges
|
|
1282
|
+
.map((edge) => this.normalizeLangGraphWorkflowEdge(edge))
|
|
1283
|
+
.filter((edge) => edge !== null);
|
|
1284
|
+
}
|
|
1285
|
+
shouldFollowLangGraphEdge(edge, state, executorResult) {
|
|
1286
|
+
const condition = edge.when?.toLowerCase().trim();
|
|
1287
|
+
if (!condition || condition === "always") {
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
const review = state.review?.toLowerCase() ?? "";
|
|
1291
|
+
const hasResult = Boolean(executorResult);
|
|
1292
|
+
switch (condition) {
|
|
1293
|
+
case "has_plan":
|
|
1294
|
+
return Boolean(state.plan?.trim());
|
|
1295
|
+
case "has_result":
|
|
1296
|
+
return hasResult;
|
|
1297
|
+
case "needs_review":
|
|
1298
|
+
return hasResult;
|
|
1299
|
+
case "review_ok":
|
|
1300
|
+
case "review_sufficient":
|
|
1301
|
+
return review.length > 0 && !/(incomplete|insufficient|missing|risky|risk|follow-up|followup|unclear)/i.test(review);
|
|
1302
|
+
case "review_incomplete":
|
|
1303
|
+
case "review_retry":
|
|
1304
|
+
return /(incomplete|insufficient|missing|follow-up|followup|retry|replan|unclear)/i.test(review);
|
|
1305
|
+
case "can_replan":
|
|
1306
|
+
return state.replans < 4;
|
|
1307
|
+
case "approval_approved":
|
|
1308
|
+
return state.approvalDecision === "approve";
|
|
1309
|
+
case "approval_rejected":
|
|
1310
|
+
return state.approvalDecision === "reject";
|
|
1311
|
+
case "approval_edited":
|
|
1312
|
+
return state.approvalDecision === "edit";
|
|
1313
|
+
default:
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
listLangGraphWorkflowNextNodes(workflow, nodeId, state, executorResult) {
|
|
1318
|
+
return this.listLangGraphWorkflowEdges(workflow)
|
|
1319
|
+
.filter((edge) => edge.from === nodeId && this.shouldFollowLangGraphEdge(edge, state, executorResult))
|
|
1320
|
+
.map((edge) => edge.to);
|
|
1321
|
+
}
|
|
1322
|
+
extractInvocationRequestText(request) {
|
|
1323
|
+
if (!isRecord(request) || !Array.isArray(request.messages)) {
|
|
1324
|
+
return "";
|
|
1325
|
+
}
|
|
1326
|
+
for (let index = request.messages.length - 1; index >= 0; index -= 1) {
|
|
1327
|
+
const message = request.messages[index];
|
|
1328
|
+
if (!isRecord(message)) {
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
const role = typeof message.role === "string" ? message.role : undefined;
|
|
1332
|
+
if (role !== "user") {
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
return extractMessageText(message.content);
|
|
1336
|
+
}
|
|
1337
|
+
return "";
|
|
1338
|
+
}
|
|
1339
|
+
prependSystemMessage(request, content) {
|
|
1340
|
+
if (!content.trim() || !isRecord(request) || !Array.isArray(request.messages)) {
|
|
1341
|
+
return request;
|
|
1342
|
+
}
|
|
1343
|
+
return {
|
|
1344
|
+
...request,
|
|
1345
|
+
messages: [{ role: "system", content }, ...request.messages],
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
replaceLastUserMessage(request, content) {
|
|
1349
|
+
if (!content.trim() || !isRecord(request) || !Array.isArray(request.messages)) {
|
|
1350
|
+
return request;
|
|
1351
|
+
}
|
|
1352
|
+
const messages = [...request.messages];
|
|
1353
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1354
|
+
const message = messages[index];
|
|
1355
|
+
if (!isRecord(message) || message.role !== "user") {
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
messages[index] = {
|
|
1359
|
+
...message,
|
|
1360
|
+
content,
|
|
1361
|
+
};
|
|
1362
|
+
return {
|
|
1363
|
+
...request,
|
|
1364
|
+
messages,
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
...request,
|
|
1369
|
+
messages: [...messages, { role: "user", content }],
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
resolveLangGraphSessionKey(binding, config) {
|
|
1373
|
+
const configurable = isRecord(config?.configurable) ? config?.configurable : undefined;
|
|
1374
|
+
const threadId = typeof configurable?.thread_id === "string" ? configurable.thread_id : undefined;
|
|
1375
|
+
return threadId ? `${binding.agent.id}:${threadId}` : undefined;
|
|
1376
|
+
}
|
|
1377
|
+
resolveLangGraphSessionIdentity(binding, config, options = {}) {
|
|
1378
|
+
const configurable = isRecord(config?.configurable) ? config?.configurable : undefined;
|
|
1379
|
+
const threadId = typeof configurable?.thread_id === "string" ? configurable.thread_id : undefined;
|
|
1380
|
+
if (!threadId) {
|
|
1381
|
+
return undefined;
|
|
1382
|
+
}
|
|
1383
|
+
const configuredRunId = typeof configurable?.run_id === "string" ? configurable.run_id : undefined;
|
|
1384
|
+
const runId = configuredRunId ?? options.fallbackRunId ?? threadId;
|
|
1385
|
+
return {
|
|
1386
|
+
sessionKey: `${binding.agent.id}:${threadId}:${runId}`,
|
|
1387
|
+
legacySessionKey: `${binding.agent.id}:${threadId}`,
|
|
1388
|
+
threadId,
|
|
1389
|
+
runId,
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
langGraphCheckpointNamespace(binding, identity) {
|
|
1393
|
+
return `langgraph-workflow:${binding.agent.id}:${identity.runId}`;
|
|
1394
|
+
}
|
|
1395
|
+
langGraphCheckpointConfig(binding, identity, checkpointId) {
|
|
1396
|
+
return {
|
|
1397
|
+
configurable: {
|
|
1398
|
+
thread_id: identity.threadId,
|
|
1399
|
+
checkpoint_ns: this.langGraphCheckpointNamespace(binding, identity),
|
|
1400
|
+
...(checkpointId ? { checkpoint_id: checkpointId } : {}),
|
|
1401
|
+
},
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
buildLangGraphWorkflowCheckpoint(session) {
|
|
1405
|
+
const checkpointId = `langgraph-workflow-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1406
|
+
return {
|
|
1407
|
+
v: 1,
|
|
1408
|
+
id: checkpointId,
|
|
1409
|
+
ts: new Date().toISOString(),
|
|
1410
|
+
channel_values: {
|
|
1411
|
+
workflow_session: session,
|
|
1412
|
+
},
|
|
1413
|
+
channel_versions: {
|
|
1414
|
+
workflow_session: 1,
|
|
1415
|
+
},
|
|
1416
|
+
versions_seen: {},
|
|
1417
|
+
pending_sends: [],
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
async saveLangGraphSessionToCheckpointer(binding, identity, session) {
|
|
1421
|
+
const checkpointer = this.resolveLangGraphWorkflowCheckpointer(binding);
|
|
1422
|
+
if (typeof checkpointer?.put !== "function") {
|
|
1423
|
+
return false;
|
|
1424
|
+
}
|
|
1425
|
+
await checkpointer.put(this.langGraphCheckpointConfig(binding, identity), this.buildLangGraphWorkflowCheckpoint(session), {
|
|
1426
|
+
source: "agent-harness",
|
|
1427
|
+
step: 0,
|
|
1428
|
+
writes: {
|
|
1429
|
+
workflow_session: true,
|
|
1430
|
+
},
|
|
1431
|
+
});
|
|
1432
|
+
return true;
|
|
1433
|
+
}
|
|
1434
|
+
async loadLangGraphSessionFromCheckpointer(binding, identity) {
|
|
1435
|
+
const checkpointer = this.resolveLangGraphWorkflowCheckpointer(binding);
|
|
1436
|
+
if (typeof checkpointer?.getTuple !== "function") {
|
|
1437
|
+
return undefined;
|
|
1438
|
+
}
|
|
1439
|
+
const tuple = await checkpointer.getTuple(this.langGraphCheckpointConfig(binding, identity));
|
|
1440
|
+
const checkpoint = asObject(asObject(tuple)?.checkpoint);
|
|
1441
|
+
const channelValues = asObject(checkpoint?.channel_values);
|
|
1442
|
+
const session = channelValues?.workflow_session;
|
|
1443
|
+
if (session === null || session === undefined) {
|
|
1444
|
+
return undefined;
|
|
1445
|
+
}
|
|
1446
|
+
return session;
|
|
1447
|
+
}
|
|
1448
|
+
async clearLangGraphSessionInCheckpointer(binding, identity) {
|
|
1449
|
+
const checkpointer = this.resolveLangGraphWorkflowCheckpointer(binding);
|
|
1450
|
+
if (typeof checkpointer?.put !== "function") {
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
await checkpointer.put(this.langGraphCheckpointConfig(binding, identity), {
|
|
1454
|
+
v: 1,
|
|
1455
|
+
id: `langgraph-workflow-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
1456
|
+
ts: new Date().toISOString(),
|
|
1457
|
+
channel_values: {
|
|
1458
|
+
workflow_session: null,
|
|
1459
|
+
},
|
|
1460
|
+
channel_versions: {
|
|
1461
|
+
workflow_session: 1,
|
|
1462
|
+
},
|
|
1463
|
+
versions_seen: {},
|
|
1464
|
+
pending_sends: [],
|
|
1465
|
+
}, {
|
|
1466
|
+
source: "agent-harness",
|
|
1467
|
+
step: 0,
|
|
1468
|
+
writes: {
|
|
1469
|
+
workflow_session: true,
|
|
1470
|
+
},
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
langGraphSessionFilePath(binding, identity) {
|
|
1474
|
+
return path.join(binding.harnessRuntime.runRoot, "threads", identity.threadId, "runs", identity.runId, "backend", "langgraph", "session.json");
|
|
1475
|
+
}
|
|
1476
|
+
artifactLangGraphSessionFilePath(binding, identity) {
|
|
1477
|
+
return path.join(binding.harnessRuntime.runRoot, "threads", identity.threadId, "runs", identity.runId, "artifacts", "langgraph-workflow-session.json");
|
|
1478
|
+
}
|
|
1479
|
+
legacyLangGraphSessionFilePath(binding, sessionKey) {
|
|
1480
|
+
return path.join(binding.harnessRuntime.runRoot, "langgraph-sessions", `${sessionKey.replace(/[^a-zA-Z0-9._-]+/g, "_")}.json`);
|
|
1481
|
+
}
|
|
1482
|
+
async saveLangGraphSession(binding, identity, session) {
|
|
1483
|
+
this.langGraphSessions.set(identity.sessionKey, session);
|
|
1484
|
+
const persistedToCheckpointer = await this.saveLangGraphSessionToCheckpointer(binding, identity, session);
|
|
1485
|
+
if (!persistedToCheckpointer) {
|
|
1486
|
+
const filePath = this.langGraphSessionFilePath(binding, identity);
|
|
1487
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
1488
|
+
await writeFile(filePath, JSON.stringify(session, null, 2), "utf8");
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
async loadLangGraphSession(binding, identity) {
|
|
1492
|
+
const cached = this.langGraphSessions.get(identity.sessionKey);
|
|
1493
|
+
if (cached) {
|
|
1494
|
+
return cached;
|
|
1495
|
+
}
|
|
1496
|
+
const checkpointerSession = await this.loadLangGraphSessionFromCheckpointer(binding, identity);
|
|
1497
|
+
if (checkpointerSession) {
|
|
1498
|
+
this.langGraphSessions.set(identity.sessionKey, checkpointerSession);
|
|
1499
|
+
return checkpointerSession;
|
|
1500
|
+
}
|
|
1501
|
+
try {
|
|
1502
|
+
const filePath = this.langGraphSessionFilePath(binding, identity);
|
|
1503
|
+
const content = await readFile(filePath, "utf8");
|
|
1504
|
+
const parsed = JSON.parse(content);
|
|
1505
|
+
this.langGraphSessions.set(identity.sessionKey, parsed);
|
|
1506
|
+
return parsed;
|
|
1507
|
+
}
|
|
1508
|
+
catch {
|
|
1509
|
+
try {
|
|
1510
|
+
const artifactPath = this.artifactLangGraphSessionFilePath(binding, identity);
|
|
1511
|
+
const content = await readFile(artifactPath, "utf8");
|
|
1512
|
+
const parsed = JSON.parse(content);
|
|
1513
|
+
this.langGraphSessions.set(identity.sessionKey, parsed);
|
|
1514
|
+
return parsed;
|
|
1515
|
+
}
|
|
1516
|
+
catch {
|
|
1517
|
+
try {
|
|
1518
|
+
const legacyPath = this.legacyLangGraphSessionFilePath(binding, identity.legacySessionKey);
|
|
1519
|
+
const content = await readFile(legacyPath, "utf8");
|
|
1520
|
+
const parsed = JSON.parse(content);
|
|
1521
|
+
this.langGraphSessions.set(identity.sessionKey, parsed);
|
|
1522
|
+
return parsed;
|
|
1523
|
+
}
|
|
1524
|
+
catch {
|
|
1525
|
+
return undefined;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
async clearLangGraphSession(binding, identity) {
|
|
1531
|
+
this.langGraphSessions.delete(identity.sessionKey);
|
|
1532
|
+
await this.clearLangGraphSessionInCheckpointer(binding, identity);
|
|
1533
|
+
const filePath = this.langGraphSessionFilePath(binding, identity);
|
|
1534
|
+
await rm(filePath, { force: true });
|
|
1535
|
+
await rm(this.artifactLangGraphSessionFilePath(binding, identity), { force: true });
|
|
1536
|
+
await rm(this.legacyLangGraphSessionFilePath(binding, identity.legacySessionKey), { force: true });
|
|
1537
|
+
}
|
|
1538
|
+
async invokeWorkflowNodeModel(model, systemPrompt, userContent) {
|
|
1539
|
+
const resolved = (await this.resolveModel(model));
|
|
1540
|
+
if (!resolved.invoke) {
|
|
1541
|
+
throw new Error(`Workflow model ${model.id} does not support invoke()`);
|
|
1542
|
+
}
|
|
1543
|
+
const result = await resolved.invoke([
|
|
1544
|
+
{ role: "system", content: systemPrompt },
|
|
1545
|
+
{ role: "user", content: userContent },
|
|
1546
|
+
]);
|
|
1547
|
+
return sanitizeVisibleText(typeof result === "string"
|
|
1548
|
+
? result
|
|
1549
|
+
: typeof result?.content === "string"
|
|
1550
|
+
? result.content
|
|
1551
|
+
: JSON.stringify(result));
|
|
1552
|
+
}
|
|
1553
|
+
async invokeLangGraphSpecialist(binding, specialistName, userInputText, workflowState, activeResult, config) {
|
|
1554
|
+
const params = getBindingLangChainParams(binding);
|
|
1555
|
+
const resolvedSubagents = await this.resolveSubagents(params.subagents ?? [], binding);
|
|
1556
|
+
const specialist = resolvedSubagents.find((candidate) => candidate.name === specialistName);
|
|
1557
|
+
if (!specialist) {
|
|
1558
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow references unknown specialist ${specialistName}`);
|
|
1559
|
+
}
|
|
1560
|
+
const model = (specialist.model
|
|
1561
|
+
? await this.resolveModel(specialist.model)
|
|
1562
|
+
: await this.resolveModel(params.model));
|
|
1563
|
+
const tools = specialist.tools ? this.resolveTools(specialist.tools, binding) : [];
|
|
1564
|
+
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
1565
|
+
throw new Error(`Specialist ${specialist.name} configures ${tools.length} tool(s), but its resolved model does not support tool binding.`);
|
|
1566
|
+
}
|
|
1567
|
+
const specialistRunnable = createAgent({
|
|
1568
|
+
...(specialist.passthrough ?? {}),
|
|
1569
|
+
model: model,
|
|
1570
|
+
tools: tools,
|
|
1571
|
+
systemPrompt: specialist.systemPrompt,
|
|
1572
|
+
responseFormat: specialist.responseFormat,
|
|
1573
|
+
contextSchema: specialist.contextSchema,
|
|
1574
|
+
middleware: (specialist.middleware ?? []),
|
|
1575
|
+
includeAgentName: "inline",
|
|
1576
|
+
name: specialist.name,
|
|
1577
|
+
description: specialist.description,
|
|
1578
|
+
});
|
|
1579
|
+
const specialistPrompt = [
|
|
1580
|
+
`User request:\n${userInputText}`,
|
|
1581
|
+
...(workflowState.plan ? ["", `Workflow plan:\n${workflowState.plan}`] : []),
|
|
1582
|
+
...(workflowState.review ? ["", `Workflow review:\n${workflowState.review}`] : []),
|
|
1583
|
+
...(activeResult ? ["", `Current executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1584
|
+
"",
|
|
1585
|
+
"Complete the delegated specialist work and return concise results.",
|
|
1586
|
+
].join("\n");
|
|
1587
|
+
return specialistRunnable.invoke({
|
|
1588
|
+
messages: [{ role: "user", content: specialistPrompt }],
|
|
1589
|
+
}, config);
|
|
1590
|
+
}
|
|
1591
|
+
async createLangGraphRunnable(binding) {
|
|
1592
|
+
const workflow = getBindingLangGraphWorkflow(binding) ?? resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), getBindingAdapterConfig(binding));
|
|
1593
|
+
if (!workflow) {
|
|
1594
|
+
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow or execution.config.preset`);
|
|
1595
|
+
}
|
|
1596
|
+
const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
|
|
1597
|
+
const nodes = this.listLangGraphWorkflowNodes(workflow);
|
|
1598
|
+
if (!entryNode || nodes.length === 0) {
|
|
1599
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow must define entryNode and nodes`);
|
|
1600
|
+
}
|
|
1601
|
+
const nodeMap = new Map(nodes.map((node) => [node.id, node]));
|
|
1602
|
+
if (!nodeMap.has(entryNode)) {
|
|
1603
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow entryNode ${entryNode} does not match any node id`);
|
|
1604
|
+
}
|
|
1605
|
+
const baseParams = getBindingLangChainParams(binding);
|
|
1606
|
+
const passthroughWithoutWorkflow = {
|
|
1607
|
+
...(baseParams.passthrough ?? {}),
|
|
1608
|
+
};
|
|
1609
|
+
delete passthroughWithoutWorkflow.workflow;
|
|
1610
|
+
delete passthroughWithoutWorkflow.langgraph;
|
|
1611
|
+
const executorRunnable = await this.createLangChainRunnable(binding, {
|
|
1612
|
+
passthroughOverride: passthroughWithoutWorkflow,
|
|
1613
|
+
});
|
|
1614
|
+
return {
|
|
1615
|
+
invoke: async (request, config) => {
|
|
1616
|
+
const sessionIdentity = this.resolveLangGraphSessionIdentity(binding, config);
|
|
1617
|
+
const resumedSession = request instanceof Command && sessionIdentity ? await this.loadLangGraphSession(binding, sessionIdentity) : undefined;
|
|
1618
|
+
const resumePayload = request instanceof Command ? request.resume : undefined;
|
|
1619
|
+
const userInputText = resumedSession ? this.extractInvocationRequestText(resumedSession.request) : this.extractInvocationRequestText(request);
|
|
1620
|
+
let activeRequest = resumedSession?.request ?? request;
|
|
1621
|
+
let activeResult = resumedSession?.result;
|
|
1622
|
+
const workflowState = resumedSession?.state
|
|
1623
|
+
? { ...resumedSession.state }
|
|
1624
|
+
: {
|
|
1625
|
+
iterations: 0,
|
|
1626
|
+
replans: 0,
|
|
1627
|
+
};
|
|
1628
|
+
const visited = new Set();
|
|
1629
|
+
let currentNodeId = resumedSession?.nextNodeId ?? entryNode;
|
|
1630
|
+
if (resumePayload !== undefined) {
|
|
1631
|
+
if (typeof resumePayload === "string" && (resumePayload === "approve" || resumePayload === "reject")) {
|
|
1632
|
+
workflowState.approvalDecision = resumePayload;
|
|
1633
|
+
}
|
|
1634
|
+
else if (isRecord(resumePayload) && resumePayload.decision === "edit" && resumePayload.editedInput) {
|
|
1635
|
+
workflowState.approvalDecision = "edit";
|
|
1636
|
+
activeRequest = this.replaceLastUserMessage(activeRequest, String(resumePayload.editedInput));
|
|
1637
|
+
}
|
|
1638
|
+
if (sessionIdentity) {
|
|
1639
|
+
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
for (let iteration = 0; currentNodeId; iteration += 1) {
|
|
1643
|
+
workflowState.iterations = iteration + 1;
|
|
1644
|
+
if (iteration >= 24) {
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
const loopKey = `${currentNodeId}:${workflowState.replans}:${Boolean(activeResult)}`;
|
|
1648
|
+
if (visited.has(loopKey)) {
|
|
1649
|
+
break;
|
|
1650
|
+
}
|
|
1651
|
+
visited.add(loopKey);
|
|
1652
|
+
const node = nodeMap.get(currentNodeId);
|
|
1653
|
+
if (!node) {
|
|
1654
|
+
break;
|
|
1655
|
+
}
|
|
1656
|
+
workflowState.lastNodeId = currentNodeId;
|
|
1657
|
+
if (node.kind === "planner") {
|
|
1658
|
+
const plannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1659
|
+
const plannerPrompt = node.prompt ?? [
|
|
1660
|
+
"You are a LangGraph workflow planner.",
|
|
1661
|
+
"Write a concise execution plan for the user request.",
|
|
1662
|
+
"Keep it brief and actionable.",
|
|
1663
|
+
].join(" ");
|
|
1664
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(plannerModel, plannerPrompt, userInputText);
|
|
1665
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Workflow plan:\n${workflowState.plan}`);
|
|
1666
|
+
}
|
|
1667
|
+
else if (node.kind === "replanner") {
|
|
1668
|
+
const replannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1669
|
+
const replannerPrompt = node.prompt ?? [
|
|
1670
|
+
"You are a LangGraph workflow replanner.",
|
|
1671
|
+
"Refine the execution plan based on the current result and review feedback.",
|
|
1672
|
+
"Return an updated concise plan only.",
|
|
1673
|
+
].join(" ");
|
|
1674
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(replannerModel, replannerPrompt, [
|
|
1675
|
+
`User request:\n${userInputText}`,
|
|
1676
|
+
...(workflowState.plan ? ["", `Current plan:\n${workflowState.plan}`] : []),
|
|
1677
|
+
...(workflowState.review ? ["", `Review feedback:\n${workflowState.review}`] : []),
|
|
1678
|
+
...(activeResult ? ["", `Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1679
|
+
].join("\n"));
|
|
1680
|
+
workflowState.replans += 1;
|
|
1681
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Updated workflow plan:\n${workflowState.plan}`);
|
|
1682
|
+
}
|
|
1683
|
+
else if (node.kind === "executor") {
|
|
1684
|
+
activeResult = await executorRunnable.invoke(activeRequest, config);
|
|
1685
|
+
}
|
|
1686
|
+
else if (node.kind === "specialist") {
|
|
1687
|
+
if (!node.specialist) {
|
|
1688
|
+
throw new Error(`LangGraph agent ${binding.agent.id} specialist node ${node.id} requires specialist`);
|
|
1689
|
+
}
|
|
1690
|
+
activeResult = await this.invokeLangGraphSpecialist(binding, node.specialist, userInputText, workflowState, activeResult, config);
|
|
1691
|
+
}
|
|
1692
|
+
else if (node.kind === "reviewer" && activeResult) {
|
|
1693
|
+
const reviewerModel = getBindingRuntimeModel(binding, "review") ?? baseParams.model;
|
|
1694
|
+
const reviewerPrompt = node.prompt ?? [
|
|
1695
|
+
"You are a LangGraph workflow reviewer.",
|
|
1696
|
+
"Review the executor result and state whether it appears sufficient.",
|
|
1697
|
+
"Call out missing verification or obvious risks briefly.",
|
|
1698
|
+
].join(" ");
|
|
1699
|
+
workflowState.review = await this.invokeWorkflowNodeModel(reviewerModel, reviewerPrompt, [
|
|
1700
|
+
`User request:\n${userInputText}`,
|
|
1701
|
+
"",
|
|
1702
|
+
`Executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1703
|
+
].join("\n"));
|
|
1704
|
+
}
|
|
1705
|
+
else if (node.kind === "approval") {
|
|
1706
|
+
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1707
|
+
const nextNodeId = nextNodes[0];
|
|
1708
|
+
if (resumePayload === undefined) {
|
|
1709
|
+
if (sessionIdentity) {
|
|
1710
|
+
await this.saveLangGraphSession(binding, sessionIdentity, {
|
|
1711
|
+
request: activeRequest,
|
|
1712
|
+
result: activeResult,
|
|
1713
|
+
state: { ...workflowState },
|
|
1714
|
+
nextNodeId,
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
return {
|
|
1718
|
+
__interrupt__: [{
|
|
1719
|
+
toolName: "workflow_approval",
|
|
1720
|
+
toolId: `workflow-approval-${binding.agent.id}-${node.id}`,
|
|
1721
|
+
allowedDecisions: ["approve", "edit", "reject"],
|
|
1722
|
+
inputPreview: {
|
|
1723
|
+
nodeId: node.id,
|
|
1724
|
+
agentId: binding.agent.id,
|
|
1725
|
+
...(workflowState.plan ? { plan: workflowState.plan } : {}),
|
|
1726
|
+
...(workflowState.review ? { review: workflowState.review } : {}),
|
|
1727
|
+
},
|
|
1728
|
+
}],
|
|
1729
|
+
messages: activeResult?.messages ?? [],
|
|
1730
|
+
workflow: {
|
|
1731
|
+
...workflowState,
|
|
1732
|
+
pendingApprovalNodeId: node.id,
|
|
1733
|
+
},
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
else if ((node.kind === "finalizer" || node.kind === "final") && activeResult) {
|
|
1738
|
+
const finalModel = getBindingRuntimeModel(binding, "final") ?? baseParams.model;
|
|
1739
|
+
const finalPrompt = node.prompt ?? [
|
|
1740
|
+
"You are a LangGraph workflow finalizer.",
|
|
1741
|
+
"Rewrite the current result into a concise user-facing answer.",
|
|
1742
|
+
"Preserve facts and caveats. Do not invent work that was not completed.",
|
|
1743
|
+
].join(" ");
|
|
1744
|
+
const finalized = await this.invokeWorkflowNodeModel(finalModel, finalPrompt, [
|
|
1745
|
+
`User request:\n${userInputText}`,
|
|
1746
|
+
...(workflowState.plan ? ["", `Plan:\n${workflowState.plan}`] : []),
|
|
1747
|
+
...(workflowState.review ? ["", `Review:\n${workflowState.review}`] : []),
|
|
1748
|
+
"",
|
|
1749
|
+
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1750
|
+
].join("\n"));
|
|
1751
|
+
activeResult = {
|
|
1752
|
+
...activeResult,
|
|
1753
|
+
output: finalized,
|
|
1754
|
+
messages: [{ role: "assistant", content: finalized }],
|
|
1755
|
+
workflow: {
|
|
1756
|
+
...workflowState,
|
|
1757
|
+
},
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1761
|
+
currentNodeId = nextNodes[0];
|
|
1762
|
+
}
|
|
1763
|
+
if (!activeResult) {
|
|
1764
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an executor node`);
|
|
1765
|
+
}
|
|
1766
|
+
if (sessionIdentity) {
|
|
1767
|
+
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
1768
|
+
}
|
|
1769
|
+
if ((workflowState.plan || workflowState.review || workflowState.replans > 0) && !isRecord(activeResult.workflow)) {
|
|
1770
|
+
activeResult = {
|
|
1771
|
+
...activeResult,
|
|
1772
|
+
workflow: {
|
|
1773
|
+
...workflowState,
|
|
1774
|
+
},
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
return activeResult;
|
|
1778
|
+
},
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
extractExecutedToolResults(result) {
|
|
1782
|
+
const metadata = asObject(result?.metadata);
|
|
1783
|
+
if (Array.isArray(metadata?.executedToolResults)) {
|
|
1784
|
+
return metadata.executedToolResults;
|
|
1785
|
+
}
|
|
1786
|
+
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
1787
|
+
return messages.flatMap((message) => {
|
|
1788
|
+
const typed = asObject(message);
|
|
1789
|
+
const kwargs = asObject(typed?.kwargs);
|
|
1790
|
+
const typeId = Array.isArray(typed?.id) ? typed.id.at(-1) : undefined;
|
|
1791
|
+
const runtimeType = typeof typed?.type === "string" ? typed.type : undefined;
|
|
1792
|
+
if (typeId !== "ToolMessage" && runtimeType !== "tool") {
|
|
1793
|
+
return [];
|
|
1794
|
+
}
|
|
1795
|
+
const toolName = typeof typed?.name === "string"
|
|
1796
|
+
? typed.name
|
|
1797
|
+
: typeof kwargs?.name === "string"
|
|
1798
|
+
? kwargs.name
|
|
1799
|
+
: "tool";
|
|
1800
|
+
const output = typed?.content ??
|
|
1801
|
+
kwargs?.content ??
|
|
1802
|
+
"";
|
|
1803
|
+
return [{
|
|
1804
|
+
toolName,
|
|
1805
|
+
output,
|
|
1806
|
+
}];
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
extractLangGraphResultOutput(result) {
|
|
1810
|
+
if (!result) {
|
|
1811
|
+
return "";
|
|
1812
|
+
}
|
|
1813
|
+
return sanitizeVisibleText(extractVisibleOutput(result) ||
|
|
1814
|
+
extractToolFallbackContext(result) ||
|
|
1815
|
+
(typeof result.output === "string" ? result.output : ""));
|
|
1816
|
+
}
|
|
1817
|
+
async *streamLangGraphWorkflow(binding, input, threadId, history = [], options = {}) {
|
|
1818
|
+
const workflow = getBindingLangGraphWorkflow(binding) ?? resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), getBindingAdapterConfig(binding));
|
|
1819
|
+
if (!workflow) {
|
|
1820
|
+
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow or execution.config.preset`);
|
|
1821
|
+
}
|
|
1822
|
+
const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
|
|
1823
|
+
const nodes = this.listLangGraphWorkflowNodes(workflow);
|
|
1824
|
+
if (!entryNode || nodes.length === 0) {
|
|
1825
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow must define entryNode and nodes`);
|
|
1826
|
+
}
|
|
1827
|
+
const nodeMap = new Map(nodes.map((node) => [node.id, node]));
|
|
1828
|
+
if (!nodeMap.has(entryNode)) {
|
|
1829
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow entryNode ${entryNode} does not match any node id`);
|
|
1830
|
+
}
|
|
1831
|
+
const baseParams = getBindingLangChainParams(binding);
|
|
1832
|
+
const passthroughWithoutWorkflow = {
|
|
1833
|
+
...(baseParams.passthrough ?? {}),
|
|
1834
|
+
};
|
|
1835
|
+
delete passthroughWithoutWorkflow.workflow;
|
|
1836
|
+
delete passthroughWithoutWorkflow.langgraph;
|
|
1837
|
+
const executorRunnable = await this.createLangChainRunnable(binding, {
|
|
1838
|
+
passthroughOverride: passthroughWithoutWorkflow,
|
|
1839
|
+
});
|
|
1840
|
+
const request = this.buildInvocationRequest(binding, history, input, options);
|
|
1841
|
+
const sessionIdentity = this.resolveLangGraphSessionIdentity(binding, { configurable: { thread_id: threadId, run_id: options.runId } }, {
|
|
1842
|
+
fallbackRunId: options.runId,
|
|
1843
|
+
});
|
|
1844
|
+
const userInputText = this.extractInvocationRequestText(request);
|
|
1845
|
+
let activeRequest = request;
|
|
1846
|
+
let activeResult;
|
|
1847
|
+
const workflowState = {
|
|
1848
|
+
iterations: 0,
|
|
1849
|
+
replans: 0,
|
|
1850
|
+
};
|
|
1851
|
+
const visited = new Set();
|
|
1852
|
+
let currentNodeId = entryNode;
|
|
1853
|
+
let emittedOutput = "";
|
|
1854
|
+
for (let iteration = 0; currentNodeId; iteration += 1) {
|
|
1855
|
+
workflowState.iterations = iteration + 1;
|
|
1856
|
+
if (iteration >= 24) {
|
|
1857
|
+
break;
|
|
1858
|
+
}
|
|
1859
|
+
const loopKey = `${currentNodeId}:${workflowState.replans}:${Boolean(activeResult)}`;
|
|
1860
|
+
if (visited.has(loopKey)) {
|
|
1861
|
+
break;
|
|
1862
|
+
}
|
|
1863
|
+
visited.add(loopKey);
|
|
1864
|
+
const node = nodeMap.get(currentNodeId);
|
|
1865
|
+
if (!node) {
|
|
1866
|
+
break;
|
|
1867
|
+
}
|
|
1868
|
+
workflowState.lastNodeId = currentNodeId;
|
|
1869
|
+
yield { kind: "step", content: `langgraph node ${node.id} (${node.kind})` };
|
|
1870
|
+
if (node.kind === "planner") {
|
|
1871
|
+
const plannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1872
|
+
const plannerPrompt = node.prompt ?? [
|
|
1873
|
+
"You are a LangGraph workflow planner.",
|
|
1874
|
+
"Write a concise execution plan for the user request.",
|
|
1875
|
+
"Keep it brief and actionable.",
|
|
1876
|
+
].join(" ");
|
|
1877
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(plannerModel, plannerPrompt, userInputText);
|
|
1878
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Workflow plan:\n${workflowState.plan}`);
|
|
1879
|
+
if (workflowState.plan) {
|
|
1880
|
+
yield { kind: "reasoning", content: workflowState.plan };
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
else if (node.kind === "replanner") {
|
|
1884
|
+
const replannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1885
|
+
const replannerPrompt = node.prompt ?? [
|
|
1886
|
+
"You are a LangGraph workflow replanner.",
|
|
1887
|
+
"Refine the execution plan based on the current result and review feedback.",
|
|
1888
|
+
"Return an updated concise plan only.",
|
|
1889
|
+
].join(" ");
|
|
1890
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(replannerModel, replannerPrompt, [
|
|
1891
|
+
`User request:\n${userInputText}`,
|
|
1892
|
+
...(workflowState.plan ? ["", `Current plan:\n${workflowState.plan}`] : []),
|
|
1893
|
+
...(workflowState.review ? ["", `Review feedback:\n${workflowState.review}`] : []),
|
|
1894
|
+
...(activeResult ? ["", `Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1895
|
+
].join("\n"));
|
|
1896
|
+
workflowState.replans += 1;
|
|
1897
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Updated workflow plan:\n${workflowState.plan}`);
|
|
1898
|
+
if (workflowState.plan) {
|
|
1899
|
+
yield { kind: "reasoning", content: workflowState.plan };
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
else if (node.kind === "executor") {
|
|
1903
|
+
if (typeof executorRunnable.streamEvents === "function") {
|
|
1904
|
+
const executorEvents = await executorRunnable.streamEvents(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, version: "v2", ...(options.context ? { context: options.context } : {}) });
|
|
1905
|
+
let executorOutput = "";
|
|
1906
|
+
const seenTerminalOutputs = new Set();
|
|
1907
|
+
for await (const event of executorEvents) {
|
|
1908
|
+
const reasoning = extractReasoningStreamOutput(event);
|
|
1909
|
+
if (reasoning) {
|
|
1910
|
+
yield { kind: "reasoning", content: reasoning };
|
|
1911
|
+
}
|
|
1912
|
+
const toolResult = extractToolResult(event);
|
|
1913
|
+
if (toolResult) {
|
|
1914
|
+
yield {
|
|
1915
|
+
kind: "tool-result",
|
|
1916
|
+
toolName: toolResult.toolName,
|
|
1917
|
+
output: toolResult.output,
|
|
1918
|
+
isError: toolResult.isError,
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
const visibleStreamOutput = extractVisibleStreamOutput(event);
|
|
1922
|
+
if (visibleStreamOutput) {
|
|
1923
|
+
executorOutput = computeIncrementalOutput(executorOutput, visibleStreamOutput).accumulated;
|
|
1924
|
+
}
|
|
1925
|
+
const terminalOutput = extractTerminalStreamOutput(event);
|
|
1926
|
+
if (terminalOutput) {
|
|
1927
|
+
const outputKey = normalizeTerminalOutputKey(terminalOutput);
|
|
1928
|
+
if (outputKey && seenTerminalOutputs.has(outputKey)) {
|
|
1929
|
+
continue;
|
|
1930
|
+
}
|
|
1931
|
+
if (outputKey) {
|
|
1932
|
+
seenTerminalOutputs.add(outputKey);
|
|
1933
|
+
}
|
|
1934
|
+
executorOutput = computeIncrementalOutput(executorOutput, sanitizeVisibleText(terminalOutput)).accumulated;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
activeResult = executorOutput
|
|
1938
|
+
? {
|
|
1939
|
+
output: executorOutput,
|
|
1940
|
+
messages: [{ role: "assistant", content: executorOutput }],
|
|
1941
|
+
}
|
|
1942
|
+
: await executorRunnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
1943
|
+
}
|
|
1944
|
+
else {
|
|
1945
|
+
activeResult = await executorRunnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
1946
|
+
}
|
|
1947
|
+
for (const toolResult of this.extractExecutedToolResults(activeResult)) {
|
|
1948
|
+
yield {
|
|
1949
|
+
kind: "tool-result",
|
|
1950
|
+
toolName: toolResult.toolName,
|
|
1951
|
+
output: toolResult.output,
|
|
1952
|
+
isError: toolResult.isError,
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
else if (node.kind === "specialist") {
|
|
1957
|
+
if (!node.specialist) {
|
|
1958
|
+
throw new Error(`LangGraph agent ${binding.agent.id} specialist node ${node.id} requires specialist`);
|
|
1959
|
+
}
|
|
1960
|
+
activeResult = await this.invokeLangGraphSpecialist(binding, node.specialist, userInputText, workflowState, activeResult, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
1961
|
+
for (const toolResult of this.extractExecutedToolResults(activeResult)) {
|
|
1962
|
+
yield {
|
|
1963
|
+
kind: "tool-result",
|
|
1964
|
+
toolName: toolResult.toolName,
|
|
1965
|
+
output: toolResult.output,
|
|
1966
|
+
isError: toolResult.isError,
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
else if (node.kind === "reviewer" && activeResult) {
|
|
1971
|
+
const reviewerModel = getBindingRuntimeModel(binding, "review") ?? baseParams.model;
|
|
1972
|
+
const reviewerPrompt = node.prompt ?? [
|
|
1973
|
+
"You are a LangGraph workflow reviewer.",
|
|
1974
|
+
"Review the executor result and state whether it appears sufficient.",
|
|
1975
|
+
"Call out missing verification or obvious risks briefly.",
|
|
1976
|
+
].join(" ");
|
|
1977
|
+
workflowState.review = await this.invokeWorkflowNodeModel(reviewerModel, reviewerPrompt, [
|
|
1978
|
+
`User request:\n${userInputText}`,
|
|
1979
|
+
"",
|
|
1980
|
+
`Executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1981
|
+
].join("\n"));
|
|
1982
|
+
if (workflowState.review) {
|
|
1983
|
+
yield { kind: "reasoning", content: workflowState.review };
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
else if (node.kind === "approval") {
|
|
1987
|
+
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1988
|
+
const nextNodeId = nextNodes[0];
|
|
1989
|
+
const interruptPayload = {
|
|
1990
|
+
toolName: "workflow_approval",
|
|
1991
|
+
toolId: `workflow-approval-${binding.agent.id}-${node.id}`,
|
|
1992
|
+
allowedDecisions: ["approve", "edit", "reject"],
|
|
1993
|
+
inputPreview: {
|
|
1994
|
+
nodeId: node.id,
|
|
1995
|
+
agentId: binding.agent.id,
|
|
1996
|
+
...(workflowState.plan ? { plan: workflowState.plan } : {}),
|
|
1997
|
+
...(workflowState.review ? { review: workflowState.review } : {}),
|
|
1998
|
+
},
|
|
1999
|
+
};
|
|
2000
|
+
if (sessionIdentity) {
|
|
2001
|
+
await this.saveLangGraphSession(binding, sessionIdentity, {
|
|
2002
|
+
request: activeRequest,
|
|
2003
|
+
result: activeResult,
|
|
2004
|
+
state: { ...workflowState },
|
|
2005
|
+
nextNodeId,
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
yield { kind: "interrupt", content: JSON.stringify([interruptPayload]) };
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
else if ((node.kind === "finalizer" || node.kind === "final") && activeResult) {
|
|
2012
|
+
const finalModel = getBindingRuntimeModel(binding, "final") ?? baseParams.model;
|
|
2013
|
+
const finalPrompt = node.prompt ?? [
|
|
2014
|
+
"You are a LangGraph workflow finalizer.",
|
|
2015
|
+
"Rewrite the current result into a concise user-facing answer.",
|
|
2016
|
+
"Preserve facts and caveats. Do not invent work that was not completed.",
|
|
2017
|
+
].join(" ");
|
|
2018
|
+
const finalized = await this.invokeWorkflowNodeModel(finalModel, finalPrompt, [
|
|
2019
|
+
`User request:\n${userInputText}`,
|
|
2020
|
+
...(workflowState.plan ? ["", `Plan:\n${workflowState.plan}`] : []),
|
|
2021
|
+
...(workflowState.review ? ["", `Review:\n${workflowState.review}`] : []),
|
|
2022
|
+
"",
|
|
2023
|
+
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
2024
|
+
].join("\n"));
|
|
2025
|
+
activeResult = {
|
|
2026
|
+
...activeResult,
|
|
2027
|
+
output: finalized,
|
|
2028
|
+
messages: [{ role: "assistant", content: finalized }],
|
|
2029
|
+
workflow: {
|
|
2030
|
+
...workflowState,
|
|
2031
|
+
},
|
|
2032
|
+
};
|
|
2033
|
+
const nextOutput = computeIncrementalOutput(emittedOutput, finalized);
|
|
2034
|
+
emittedOutput = nextOutput.accumulated;
|
|
2035
|
+
if (nextOutput.delta) {
|
|
2036
|
+
yield { kind: "content", content: nextOutput.delta };
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
2040
|
+
currentNodeId = nextNodes[0];
|
|
2041
|
+
}
|
|
2042
|
+
if (!activeResult) {
|
|
2043
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an executor node`);
|
|
2044
|
+
}
|
|
2045
|
+
if (sessionIdentity) {
|
|
2046
|
+
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
2047
|
+
}
|
|
2048
|
+
if ((workflowState.plan || workflowState.review || workflowState.replans > 0) && !isRecord(activeResult.workflow)) {
|
|
2049
|
+
activeResult = {
|
|
2050
|
+
...activeResult,
|
|
2051
|
+
workflow: {
|
|
2052
|
+
...workflowState,
|
|
2053
|
+
},
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
const finalOutput = this.extractLangGraphResultOutput(activeResult);
|
|
2057
|
+
const nextOutput = computeIncrementalOutput(emittedOutput, finalOutput);
|
|
2058
|
+
if (nextOutput.delta) {
|
|
2059
|
+
yield { kind: "content", content: nextOutput.delta };
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
1181
2062
|
async createRunnable(binding) {
|
|
2063
|
+
if (getBindingAdapterKind(binding) === "langgraph") {
|
|
2064
|
+
return this.createLangGraphRunnable(binding);
|
|
2065
|
+
}
|
|
1182
2066
|
if (isLangChainBinding(binding)) {
|
|
1183
|
-
|
|
1184
|
-
const interruptOn = this.resolveInterruptOn(binding);
|
|
1185
|
-
const model = (await this.resolveModel(params.model));
|
|
1186
|
-
const tools = this.resolveTools(params.tools, binding);
|
|
1187
|
-
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
1188
|
-
throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
|
|
1189
|
-
}
|
|
1190
|
-
return createAgent({
|
|
1191
|
-
...(params.passthrough ?? {}),
|
|
1192
|
-
model: model,
|
|
1193
|
-
tools: tools,
|
|
1194
|
-
systemPrompt: params.systemPrompt,
|
|
1195
|
-
stateSchema: params.stateSchema,
|
|
1196
|
-
responseFormat: params.responseFormat,
|
|
1197
|
-
contextSchema: params.contextSchema,
|
|
1198
|
-
middleware: (await this.resolveMiddleware(binding, interruptOn)),
|
|
1199
|
-
checkpointer: this.resolveCheckpointer(binding),
|
|
1200
|
-
store: this.options.storeResolver?.(binding),
|
|
1201
|
-
includeAgentName: params.includeAgentName,
|
|
1202
|
-
version: params.version,
|
|
1203
|
-
name: params.name,
|
|
1204
|
-
description: params.description,
|
|
1205
|
-
});
|
|
2067
|
+
return this.createLangChainRunnable(binding);
|
|
1206
2068
|
}
|
|
1207
2069
|
const params = getBindingDeepAgentParams(binding);
|
|
1208
2070
|
if (!params) {
|
|
1209
2071
|
throw new Error(`Agent ${binding.agent.id} has no runnable params`);
|
|
1210
2072
|
}
|
|
2073
|
+
const compatibleParams = applyDeepAgentDelegationPromptCompatibility(params.model, params);
|
|
1211
2074
|
const deepAgentConfig = {
|
|
1212
|
-
...(
|
|
1213
|
-
model: (await this.resolveModel(
|
|
1214
|
-
tools: this.resolveTools(
|
|
1215
|
-
systemPrompt:
|
|
1216
|
-
responseFormat:
|
|
1217
|
-
contextSchema:
|
|
2075
|
+
...(compatibleParams.passthrough ?? {}),
|
|
2076
|
+
model: (await this.resolveModel(compatibleParams.model)),
|
|
2077
|
+
tools: this.resolveTools(compatibleParams.tools, binding),
|
|
2078
|
+
systemPrompt: compatibleParams.systemPrompt,
|
|
2079
|
+
responseFormat: compatibleParams.responseFormat,
|
|
2080
|
+
contextSchema: compatibleParams.contextSchema,
|
|
1218
2081
|
middleware: (await this.resolveMiddleware(binding)),
|
|
1219
|
-
subagents: (await this.resolveSubagents(
|
|
2082
|
+
subagents: (await this.resolveSubagents(compatibleParams.subagents, binding)),
|
|
1220
2083
|
checkpointer: this.resolveCheckpointer(binding),
|
|
1221
2084
|
store: this.options.storeResolver?.(binding),
|
|
1222
2085
|
backend: this.options.backendResolver?.(binding),
|
|
1223
2086
|
interruptOn: this.resolveInterruptOn(binding),
|
|
1224
|
-
name:
|
|
1225
|
-
memory:
|
|
2087
|
+
name: compatibleParams.name,
|
|
2088
|
+
memory: compatibleParams.memory,
|
|
1226
2089
|
skills: await materializeDeepAgentSkillSourcePaths({
|
|
1227
2090
|
workspaceRoot: binding.harnessRuntime.workspaceRoot,
|
|
1228
2091
|
runRoot: binding.harnessRuntime.runRoot,
|
|
1229
2092
|
ownerId: binding.agent.id,
|
|
1230
|
-
skillPaths:
|
|
2093
|
+
skillPaths: compatibleParams.skills,
|
|
1231
2094
|
}),
|
|
1232
|
-
generalPurposeAgent:
|
|
1233
|
-
taskDescription:
|
|
2095
|
+
generalPurposeAgent: compatibleParams.generalPurposeAgent,
|
|
2096
|
+
taskDescription: compatibleParams.taskDescription,
|
|
1234
2097
|
};
|
|
1235
2098
|
return createDeepAgent(deepAgentConfig);
|
|
1236
2099
|
}
|
|
@@ -1250,7 +2113,9 @@ export class AgentRuntimeAdapter {
|
|
|
1250
2113
|
}
|
|
1251
2114
|
}
|
|
1252
2115
|
async route(input, primaryBinding, secondaryBinding, options = {}) {
|
|
1253
|
-
const routeModelConfig =
|
|
2116
|
+
const routeModelConfig = getBindingRuntimeModel(primaryBinding, "routing") ??
|
|
2117
|
+
getBindingRuntimeModel(secondaryBinding, "routing") ??
|
|
2118
|
+
getBindingPrimaryModel(primaryBinding) ??
|
|
1254
2119
|
getBindingPrimaryModel(secondaryBinding);
|
|
1255
2120
|
if (!routeModelConfig) {
|
|
1256
2121
|
throw new Error("No router model configuration available");
|
|
@@ -1276,6 +2141,84 @@ export class AgentRuntimeAdapter {
|
|
|
1276
2141
|
? secondaryBinding.agent.id
|
|
1277
2142
|
: primaryBinding.agent.id;
|
|
1278
2143
|
}
|
|
2144
|
+
async reviewRunResult(binding, input, result) {
|
|
2145
|
+
const reviewModelConfig = getBindingRuntimeModel(binding, "review");
|
|
2146
|
+
if (!reviewModelConfig) {
|
|
2147
|
+
return null;
|
|
2148
|
+
}
|
|
2149
|
+
const reviewModel = (await this.resolveModel(reviewModelConfig));
|
|
2150
|
+
if (!reviewModel?.invoke) {
|
|
2151
|
+
throw new Error("Review model does not support invoke()");
|
|
2152
|
+
}
|
|
2153
|
+
const reviewPrompt = [
|
|
2154
|
+
"You are a runtime completion reviewer.",
|
|
2155
|
+
"Review the completed agent result against the user request.",
|
|
2156
|
+
"Respond with a concise assessment that states whether the result looks sufficient, incomplete, or risky.",
|
|
2157
|
+
"Call out missing verification or likely follow-up work when relevant.",
|
|
2158
|
+
"Keep the response brief and operator-readable.",
|
|
2159
|
+
].join(" ");
|
|
2160
|
+
const reviewResult = await reviewModel.invoke([
|
|
2161
|
+
{ role: "system", content: reviewPrompt },
|
|
2162
|
+
{
|
|
2163
|
+
role: "user",
|
|
2164
|
+
content: [
|
|
2165
|
+
`User request:\n${extractMessageText(input)}`,
|
|
2166
|
+
"",
|
|
2167
|
+
`Run state: ${result.state}`,
|
|
2168
|
+
"",
|
|
2169
|
+
`Agent result:\n${result.finalMessageText ?? result.output}`,
|
|
2170
|
+
].join("\n"),
|
|
2171
|
+
},
|
|
2172
|
+
]);
|
|
2173
|
+
const assessment = typeof reviewResult === "string"
|
|
2174
|
+
? reviewResult
|
|
2175
|
+
: typeof reviewResult?.content === "string"
|
|
2176
|
+
? reviewResult.content
|
|
2177
|
+
: JSON.stringify(reviewResult);
|
|
2178
|
+
return {
|
|
2179
|
+
assessment: sanitizeVisibleText(assessment),
|
|
2180
|
+
modelId: reviewModelConfig.id,
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
async synthesizeFinalResult(binding, input, result) {
|
|
2184
|
+
const finalModelConfig = getBindingRuntimeModel(binding, "final");
|
|
2185
|
+
if (!finalModelConfig) {
|
|
2186
|
+
return null;
|
|
2187
|
+
}
|
|
2188
|
+
const finalModel = (await this.resolveModel(finalModelConfig));
|
|
2189
|
+
if (!finalModel?.invoke) {
|
|
2190
|
+
throw new Error("Final synthesis model does not support invoke()");
|
|
2191
|
+
}
|
|
2192
|
+
const synthesisPrompt = [
|
|
2193
|
+
"You are a final response synthesizer for a runtime-managed agent system.",
|
|
2194
|
+
"Rewrite the agent result into a concise, user-facing final answer.",
|
|
2195
|
+
"Preserve important facts and caveats.",
|
|
2196
|
+
"Do not invent work that was not completed.",
|
|
2197
|
+
"If the result is already concise, keep it concise.",
|
|
2198
|
+
].join(" ");
|
|
2199
|
+
const synthesisResult = await finalModel.invoke([
|
|
2200
|
+
{ role: "system", content: synthesisPrompt },
|
|
2201
|
+
{
|
|
2202
|
+
role: "user",
|
|
2203
|
+
content: [
|
|
2204
|
+
`User request:\n${extractMessageText(input)}`,
|
|
2205
|
+
"",
|
|
2206
|
+
`Agent result:\n${result.finalMessageText ?? result.output}`,
|
|
2207
|
+
].join("\n"),
|
|
2208
|
+
},
|
|
2209
|
+
]);
|
|
2210
|
+
const synthesized = typeof synthesisResult === "string"
|
|
2211
|
+
? synthesisResult
|
|
2212
|
+
: typeof synthesisResult?.content === "string"
|
|
2213
|
+
? synthesisResult.content
|
|
2214
|
+
: JSON.stringify(synthesisResult);
|
|
2215
|
+
const finalMessageText = sanitizeVisibleText(synthesized);
|
|
2216
|
+
return {
|
|
2217
|
+
output: finalMessageText,
|
|
2218
|
+
finalMessageText,
|
|
2219
|
+
modelId: finalModelConfig.id,
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
1279
2222
|
async invoke(binding, input, threadId, runId, resumePayload, history = [], options = {}) {
|
|
1280
2223
|
const request = resumePayload === undefined
|
|
1281
2224
|
? this.buildInvocationRequest(binding, history, input, options)
|
|
@@ -1284,7 +2227,7 @@ export class AgentRuntimeAdapter {
|
|
|
1284
2227
|
const callRuntime = async (activeBinding, activeRequest) => {
|
|
1285
2228
|
return this.invokeWithProviderRetry(activeBinding, async () => {
|
|
1286
2229
|
const runnable = await this.create(activeBinding);
|
|
1287
|
-
return (await this.withTimeout(() => runnable.invoke(activeRequest, { configurable: { thread_id: threadId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(activeBinding), "agent invoke", "invoke"));
|
|
2230
|
+
return (await this.withTimeout(() => runnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: runId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(activeBinding), "agent invoke", "invoke"));
|
|
1288
2231
|
});
|
|
1289
2232
|
};
|
|
1290
2233
|
const callRuntimeWithToolParseRecovery = async (activeRequest) => {
|
|
@@ -1437,15 +2380,23 @@ export class AgentRuntimeAdapter {
|
|
|
1437
2380
|
}
|
|
1438
2381
|
async *stream(binding, input, threadId, history = [], options = {}) {
|
|
1439
2382
|
try {
|
|
2383
|
+
const adapterKind = getBindingAdapterKind(binding);
|
|
1440
2384
|
const invokeTimeoutMs = this.resolveBindingTimeout(binding);
|
|
1441
2385
|
const streamIdleTimeoutMs = this.resolveStreamIdleTimeout(binding);
|
|
1442
2386
|
const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
|
|
1443
2387
|
const primaryTools = getBindingPrimaryTools(binding);
|
|
1444
2388
|
const toolNameMapping = this.buildToolNameMapping(primaryTools);
|
|
1445
2389
|
const primaryModel = getBindingPrimaryModel(binding);
|
|
1446
|
-
const forceInvokeFallback =
|
|
2390
|
+
const forceInvokeFallback = adapterKind !== "langgraph" &&
|
|
2391
|
+
isLangChainBinding(binding) &&
|
|
1447
2392
|
primaryTools.length > 0 &&
|
|
1448
2393
|
primaryModel?.provider === "openai-compatible";
|
|
2394
|
+
if (adapterKind === "langgraph") {
|
|
2395
|
+
for await (const chunk of this.streamLangGraphWorkflow(binding, input, threadId, history, options)) {
|
|
2396
|
+
yield chunk;
|
|
2397
|
+
}
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
1449
2400
|
if (isLangChainBinding(binding)) {
|
|
1450
2401
|
const langchainParams = getBindingLangChainParams(binding);
|
|
1451
2402
|
const resolvedModel = (await this.resolveModel(langchainParams.model));
|
|
@@ -1481,7 +2432,7 @@ export class AgentRuntimeAdapter {
|
|
|
1481
2432
|
const runnable = await this.create(binding);
|
|
1482
2433
|
const request = this.buildInvocationRequest(binding, history, input, options);
|
|
1483
2434
|
if (!forceInvokeFallback && typeof runnable.streamEvents === "function") {
|
|
1484
|
-
const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
|
|
2435
|
+
const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId, run_id: options.runId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
|
|
1485
2436
|
const allowVisibleStreamDeltas = isLangChainBinding(binding);
|
|
1486
2437
|
let emittedOutput = "";
|
|
1487
2438
|
let emittedToolError = false;
|
|
@@ -1554,7 +2505,7 @@ export class AgentRuntimeAdapter {
|
|
|
1554
2505
|
}
|
|
1555
2506
|
}
|
|
1556
2507
|
if (!forceInvokeFallback && isLangChainBinding(binding) && typeof runnable.stream === "function") {
|
|
1557
|
-
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
|
|
2508
|
+
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId, run_id: options.runId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
|
|
1558
2509
|
let emitted = false;
|
|
1559
2510
|
for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs)) {
|
|
1560
2511
|
const delta = readStreamDelta(chunk);
|