@botbotgo/agent-harness 0.0.93 → 0.0.95
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 +34 -4
- package/README.zh.md +32 -2
- package/dist/config/workflows/langgraph-workflows.yaml +79 -53
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/agent-runtime-adapter.d.ts +2 -1
- package/dist/runtime/agent-runtime-adapter.js +314 -212
- package/dist/runtime/langgraph-presets.d.ts +4 -2
- package/dist/runtime/langgraph-presets.js +45 -45
- package/dist/runtime/langgraph-profiles.d.ts +6 -0
- package/dist/runtime/langgraph-profiles.js +206 -0
- package/dist/workspace/agent-binding-compiler.js +33 -4
- package/package.json +1 -1
|
@@ -19,7 +19,9 @@ import { resolveDeclaredMiddleware } from "./declared-middleware.js";
|
|
|
19
19
|
import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
|
|
20
20
|
import { getBindingAdapterKind, getBindingDeepAgentParams, getBindingAdapterConfig, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingLangGraphPreset, getBindingLangGraphWorkflow, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingRuntimeModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
|
|
21
21
|
import { readSkillMetadata } from "./support/skill-metadata.js";
|
|
22
|
+
import { resolveLangGraphProfileWorkflow } from "./langgraph-profiles.js";
|
|
22
23
|
import { resolveLangGraphPresetWorkflow } from "./langgraph-presets.js";
|
|
24
|
+
const SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS = new Set(["llm", "agent", "tool", "approval", "condition"]);
|
|
23
25
|
function countConfiguredTools(binding) {
|
|
24
26
|
return getBindingPrimaryTools(binding).length;
|
|
25
27
|
}
|
|
@@ -1243,15 +1245,64 @@ export class AgentRuntimeAdapter {
|
|
|
1243
1245
|
return null;
|
|
1244
1246
|
}
|
|
1245
1247
|
const id = typeof raw.id === "string" ? raw.id.trim() : "";
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
+
const rawKind = typeof raw.kind === "string" ? raw.kind.trim() : "";
|
|
1249
|
+
const role = typeof raw.role === "string" && raw.role.trim() ? raw.role.trim() : undefined;
|
|
1250
|
+
const agent = typeof raw.agent === "string" && raw.agent.trim() ? raw.agent.trim() : undefined;
|
|
1251
|
+
const tool = typeof raw.tool === "string" && raw.tool.trim() ? raw.tool.trim() : undefined;
|
|
1252
|
+
const args = isRecord(raw.args) ? { ...raw.args } : undefined;
|
|
1253
|
+
if (!id || !rawKind) {
|
|
1248
1254
|
return null;
|
|
1249
1255
|
}
|
|
1256
|
+
if (!SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS.has(rawKind)) {
|
|
1257
|
+
throw new Error(`Unsupported LangGraph workflow node kind ${rawKind}. Supported node kinds: ${Array.from(SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS).join(", ")}`);
|
|
1258
|
+
}
|
|
1250
1259
|
return {
|
|
1251
1260
|
id,
|
|
1252
|
-
kind,
|
|
1261
|
+
kind: rawKind,
|
|
1253
1262
|
...(typeof raw.prompt === "string" && raw.prompt.trim() ? { prompt: raw.prompt.trim() } : {}),
|
|
1254
|
-
...(
|
|
1263
|
+
...(role ? { role } : {}),
|
|
1264
|
+
...(agent ? { agent } : {}),
|
|
1265
|
+
...(tool ? { tool } : {}),
|
|
1266
|
+
...(args ? { args } : {}),
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
async invokeLangGraphToolNode(binding, toolName, userInputText, config, args) {
|
|
1270
|
+
const primaryTools = getBindingPrimaryTools(binding);
|
|
1271
|
+
const resolvedTools = this.resolveTools(primaryTools, binding);
|
|
1272
|
+
const toolNameMapping = this.buildToolNameMapping(primaryTools);
|
|
1273
|
+
const resolvedToolName = resolveModelFacingToolName(toolName, toolNameMapping, primaryTools);
|
|
1274
|
+
const executableTool = resolvedTools.find((candidate) => {
|
|
1275
|
+
if (!hasCallableToolHandler(candidate)) {
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
const candidateName = typeof candidate.name === "string" ? candidate.name : undefined;
|
|
1279
|
+
return candidateName === toolName || candidateName === resolvedToolName;
|
|
1280
|
+
});
|
|
1281
|
+
if (!executableTool) {
|
|
1282
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow references unknown tool ${toolName}`);
|
|
1283
|
+
}
|
|
1284
|
+
const invokeMethod = typeof executableTool.invoke === "function"
|
|
1285
|
+
? executableTool.invoke.bind(executableTool)
|
|
1286
|
+
: typeof executableTool.call === "function"
|
|
1287
|
+
? executableTool.call.bind(executableTool)
|
|
1288
|
+
: typeof executableTool.func === "function"
|
|
1289
|
+
? executableTool.func.bind(executableTool)
|
|
1290
|
+
: undefined;
|
|
1291
|
+
if (typeof invokeMethod !== "function") {
|
|
1292
|
+
throw new Error(`LangGraph workflow tool ${toolName} is not executable`);
|
|
1293
|
+
}
|
|
1294
|
+
const input = args && Object.keys(args).length > 0 ? args : { input: userInputText };
|
|
1295
|
+
const output = await invokeMethod(input, config);
|
|
1296
|
+
const finalText = stringifyToolOutput(output);
|
|
1297
|
+
return {
|
|
1298
|
+
output: finalText,
|
|
1299
|
+
messages: [{ role: "assistant", content: finalText }],
|
|
1300
|
+
metadata: {
|
|
1301
|
+
executedToolResults: [{
|
|
1302
|
+
toolName: resolvedToolName,
|
|
1303
|
+
output,
|
|
1304
|
+
}],
|
|
1305
|
+
},
|
|
1255
1306
|
};
|
|
1256
1307
|
}
|
|
1257
1308
|
listLangGraphWorkflowNodes(workflow) {
|
|
@@ -1550,48 +1601,51 @@ export class AgentRuntimeAdapter {
|
|
|
1550
1601
|
? result.content
|
|
1551
1602
|
: JSON.stringify(result));
|
|
1552
1603
|
}
|
|
1553
|
-
async
|
|
1604
|
+
async invokeLangGraphAgentNode(binding, agentName, userInputText, workflowState, activeResult, config) {
|
|
1554
1605
|
const params = getBindingLangChainParams(binding);
|
|
1555
1606
|
const resolvedSubagents = await this.resolveSubagents(params.subagents ?? [], binding);
|
|
1556
|
-
const
|
|
1557
|
-
if (!
|
|
1558
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow references unknown
|
|
1607
|
+
const delegatedAgent = resolvedSubagents.find((candidate) => candidate.name === agentName);
|
|
1608
|
+
if (!delegatedAgent) {
|
|
1609
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow references unknown subagent ${agentName}`);
|
|
1559
1610
|
}
|
|
1560
|
-
const model = (
|
|
1561
|
-
? await this.resolveModel(
|
|
1611
|
+
const model = (delegatedAgent.model
|
|
1612
|
+
? await this.resolveModel(delegatedAgent.model)
|
|
1562
1613
|
: await this.resolveModel(params.model));
|
|
1563
|
-
const tools =
|
|
1614
|
+
const tools = delegatedAgent.tools ? this.resolveTools(delegatedAgent.tools, binding) : [];
|
|
1564
1615
|
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
1565
|
-
throw new Error(`
|
|
1616
|
+
throw new Error(`Subagent ${delegatedAgent.name} configures ${tools.length} tool(s), but its resolved model does not support tool binding.`);
|
|
1566
1617
|
}
|
|
1567
|
-
const
|
|
1568
|
-
...(
|
|
1618
|
+
const delegatedRunnable = createAgent({
|
|
1619
|
+
...(delegatedAgent.passthrough ?? {}),
|
|
1569
1620
|
model: model,
|
|
1570
1621
|
tools: tools,
|
|
1571
|
-
systemPrompt:
|
|
1572
|
-
responseFormat:
|
|
1573
|
-
contextSchema:
|
|
1574
|
-
middleware: (
|
|
1622
|
+
systemPrompt: delegatedAgent.systemPrompt,
|
|
1623
|
+
responseFormat: delegatedAgent.responseFormat,
|
|
1624
|
+
contextSchema: delegatedAgent.contextSchema,
|
|
1625
|
+
middleware: (delegatedAgent.middleware ?? []),
|
|
1575
1626
|
includeAgentName: "inline",
|
|
1576
|
-
name:
|
|
1577
|
-
description:
|
|
1627
|
+
name: delegatedAgent.name,
|
|
1628
|
+
description: delegatedAgent.description,
|
|
1578
1629
|
});
|
|
1579
|
-
const
|
|
1630
|
+
const delegatedPrompt = [
|
|
1580
1631
|
`User request:\n${userInputText}`,
|
|
1581
1632
|
...(workflowState.plan ? ["", `Workflow plan:\n${workflowState.plan}`] : []),
|
|
1582
1633
|
...(workflowState.review ? ["", `Workflow review:\n${workflowState.review}`] : []),
|
|
1583
1634
|
...(activeResult ? ["", `Current executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1584
1635
|
"",
|
|
1585
|
-
"Complete the delegated
|
|
1636
|
+
"Complete the delegated subagent work and return concise results.",
|
|
1586
1637
|
].join("\n");
|
|
1587
|
-
return
|
|
1588
|
-
messages: [{ role: "user", content:
|
|
1638
|
+
return delegatedRunnable.invoke({
|
|
1639
|
+
messages: [{ role: "user", content: delegatedPrompt }],
|
|
1589
1640
|
}, config);
|
|
1590
1641
|
}
|
|
1591
1642
|
async createLangGraphRunnable(binding) {
|
|
1592
|
-
const
|
|
1643
|
+
const adapterConfig = getBindingAdapterConfig(binding);
|
|
1644
|
+
const workflow = getBindingLangGraphWorkflow(binding) ??
|
|
1645
|
+
resolveLangGraphProfileWorkflow(typeof adapterConfig.profile === "string" ? adapterConfig.profile : undefined, typeof adapterConfig.with === "object" && adapterConfig.with ? adapterConfig.with : {}) ??
|
|
1646
|
+
resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), adapterConfig);
|
|
1593
1647
|
if (!workflow) {
|
|
1594
|
-
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow or execution.config.preset`);
|
|
1648
|
+
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow, execution.config.profile, or execution.config.preset`);
|
|
1595
1649
|
}
|
|
1596
1650
|
const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
|
|
1597
1651
|
const nodes = this.listLangGraphWorkflowNodes(workflow);
|
|
@@ -1654,53 +1708,96 @@ export class AgentRuntimeAdapter {
|
|
|
1654
1708
|
break;
|
|
1655
1709
|
}
|
|
1656
1710
|
workflowState.lastNodeId = currentNodeId;
|
|
1657
|
-
if (node.kind === "
|
|
1658
|
-
const
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1711
|
+
if (node.kind === "llm") {
|
|
1712
|
+
const nodeRole = node.role?.toLowerCase() ?? "llm";
|
|
1713
|
+
if (nodeRole === "planner") {
|
|
1714
|
+
const plannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1715
|
+
const plannerPrompt = node.prompt ?? [
|
|
1716
|
+
"You are a LangGraph workflow planner.",
|
|
1717
|
+
"Write a concise execution plan for the user request.",
|
|
1718
|
+
"Keep it brief and actionable.",
|
|
1719
|
+
].join(" ");
|
|
1720
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(plannerModel, plannerPrompt, userInputText);
|
|
1721
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Workflow plan:\n${workflowState.plan}`);
|
|
1722
|
+
}
|
|
1723
|
+
else if (nodeRole === "replanner") {
|
|
1724
|
+
const replannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1725
|
+
const replannerPrompt = node.prompt ?? [
|
|
1726
|
+
"You are a LangGraph workflow replanner.",
|
|
1727
|
+
"Refine the execution plan based on the current result and review feedback.",
|
|
1728
|
+
"Return an updated concise plan only.",
|
|
1729
|
+
].join(" ");
|
|
1730
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(replannerModel, replannerPrompt, [
|
|
1731
|
+
`User request:\n${userInputText}`,
|
|
1732
|
+
...(workflowState.plan ? ["", `Current plan:\n${workflowState.plan}`] : []),
|
|
1733
|
+
...(workflowState.review ? ["", `Review feedback:\n${workflowState.review}`] : []),
|
|
1734
|
+
...(activeResult ? ["", `Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1735
|
+
].join("\n"));
|
|
1736
|
+
workflowState.replans += 1;
|
|
1737
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Updated workflow plan:\n${workflowState.plan}`);
|
|
1738
|
+
}
|
|
1739
|
+
else if (nodeRole === "reviewer" && activeResult) {
|
|
1740
|
+
const reviewerModel = getBindingRuntimeModel(binding, "review") ?? baseParams.model;
|
|
1741
|
+
const reviewerPrompt = node.prompt ?? [
|
|
1742
|
+
"You are a LangGraph workflow reviewer.",
|
|
1743
|
+
"Review the executor result and state whether it appears sufficient.",
|
|
1744
|
+
"Call out missing verification or obvious risks briefly.",
|
|
1745
|
+
].join(" ");
|
|
1746
|
+
workflowState.review = await this.invokeWorkflowNodeModel(reviewerModel, reviewerPrompt, [
|
|
1747
|
+
`User request:\n${userInputText}`,
|
|
1748
|
+
"",
|
|
1749
|
+
`Executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1750
|
+
].join("\n"));
|
|
1751
|
+
}
|
|
1752
|
+
else if ((nodeRole === "finalizer" || nodeRole === "final") && activeResult) {
|
|
1753
|
+
const finalModel = getBindingRuntimeModel(binding, "final") ?? baseParams.model;
|
|
1754
|
+
const finalPrompt = node.prompt ?? [
|
|
1755
|
+
"You are a LangGraph workflow finalizer.",
|
|
1756
|
+
"Rewrite the current result into a concise user-facing answer.",
|
|
1757
|
+
"Preserve facts and caveats. Do not invent work that was not completed.",
|
|
1758
|
+
].join(" ");
|
|
1759
|
+
const finalized = await this.invokeWorkflowNodeModel(finalModel, finalPrompt, [
|
|
1760
|
+
`User request:\n${userInputText}`,
|
|
1761
|
+
...(workflowState.plan ? ["", `Plan:\n${workflowState.plan}`] : []),
|
|
1762
|
+
...(workflowState.review ? ["", `Review:\n${workflowState.review}`] : []),
|
|
1763
|
+
"",
|
|
1764
|
+
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1765
|
+
].join("\n"));
|
|
1766
|
+
activeResult = {
|
|
1767
|
+
...activeResult,
|
|
1768
|
+
output: finalized,
|
|
1769
|
+
messages: [{ role: "assistant", content: finalized }],
|
|
1770
|
+
workflow: {
|
|
1771
|
+
...workflowState,
|
|
1772
|
+
},
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
else {
|
|
1776
|
+
const llmModel = getBindingRuntimeModel(binding, "execution") ?? baseParams.model;
|
|
1777
|
+
const llmPrompt = node.prompt ?? "Produce the next response for this workflow node.";
|
|
1778
|
+
const llmOutput = await this.invokeWorkflowNodeModel(llmModel, llmPrompt, userInputText);
|
|
1779
|
+
activeResult = {
|
|
1780
|
+
output: llmOutput,
|
|
1781
|
+
messages: [{ role: "assistant", content: llmOutput }],
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1682
1784
|
}
|
|
1683
|
-
else if (node.kind === "
|
|
1684
|
-
|
|
1785
|
+
else if (node.kind === "agent") {
|
|
1786
|
+
if (node.agent) {
|
|
1787
|
+
activeResult = await this.invokeLangGraphAgentNode(binding, node.agent, userInputText, workflowState, activeResult, config);
|
|
1788
|
+
}
|
|
1789
|
+
else {
|
|
1790
|
+
activeResult = await executorRunnable.invoke(activeRequest, config);
|
|
1791
|
+
}
|
|
1685
1792
|
}
|
|
1686
|
-
else if (node.kind === "
|
|
1687
|
-
if (!node.
|
|
1688
|
-
throw new Error(`LangGraph agent ${binding.agent.id}
|
|
1793
|
+
else if (node.kind === "tool") {
|
|
1794
|
+
if (!node.tool) {
|
|
1795
|
+
throw new Error(`LangGraph agent ${binding.agent.id} tool node ${node.id} requires tool`);
|
|
1689
1796
|
}
|
|
1690
|
-
activeResult = await this.
|
|
1797
|
+
activeResult = await this.invokeLangGraphToolNode(binding, node.tool, userInputText, config, node.args);
|
|
1691
1798
|
}
|
|
1692
|
-
else if (node.kind === "
|
|
1693
|
-
|
|
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"));
|
|
1799
|
+
else if (node.kind === "condition") {
|
|
1800
|
+
// Condition nodes are routing-only. Edge conditions decide the next node.
|
|
1704
1801
|
}
|
|
1705
1802
|
else if (node.kind === "approval") {
|
|
1706
1803
|
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
@@ -1734,34 +1831,11 @@ export class AgentRuntimeAdapter {
|
|
|
1734
1831
|
};
|
|
1735
1832
|
}
|
|
1736
1833
|
}
|
|
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
1834
|
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1761
1835
|
currentNodeId = nextNodes[0];
|
|
1762
1836
|
}
|
|
1763
1837
|
if (!activeResult) {
|
|
1764
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an
|
|
1838
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an agent or tool node`);
|
|
1765
1839
|
}
|
|
1766
1840
|
if (sessionIdentity) {
|
|
1767
1841
|
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
@@ -1815,9 +1889,12 @@ export class AgentRuntimeAdapter {
|
|
|
1815
1889
|
(typeof result.output === "string" ? result.output : ""));
|
|
1816
1890
|
}
|
|
1817
1891
|
async *streamLangGraphWorkflow(binding, input, threadId, history = [], options = {}) {
|
|
1818
|
-
const
|
|
1892
|
+
const adapterConfig = getBindingAdapterConfig(binding);
|
|
1893
|
+
const workflow = getBindingLangGraphWorkflow(binding) ??
|
|
1894
|
+
resolveLangGraphProfileWorkflow(typeof adapterConfig.profile === "string" ? adapterConfig.profile : undefined, typeof adapterConfig.with === "object" && adapterConfig.with ? adapterConfig.with : {}) ??
|
|
1895
|
+
resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), adapterConfig);
|
|
1819
1896
|
if (!workflow) {
|
|
1820
|
-
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow or execution.config.preset`);
|
|
1897
|
+
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow, execution.config.profile, or execution.config.preset`);
|
|
1821
1898
|
}
|
|
1822
1899
|
const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
|
|
1823
1900
|
const nodes = this.listLangGraphWorkflowNodes(workflow);
|
|
@@ -1867,82 +1944,148 @@ export class AgentRuntimeAdapter {
|
|
|
1867
1944
|
}
|
|
1868
1945
|
workflowState.lastNodeId = currentNodeId;
|
|
1869
1946
|
yield { kind: "step", content: `langgraph node ${node.id} (${node.kind})` };
|
|
1870
|
-
if (node.kind === "
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1947
|
+
if (node.kind === "llm") {
|
|
1948
|
+
const nodeRole = node.role?.toLowerCase() ?? "llm";
|
|
1949
|
+
if (nodeRole === "planner") {
|
|
1950
|
+
const plannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1951
|
+
const plannerPrompt = node.prompt ?? [
|
|
1952
|
+
"You are a LangGraph workflow planner.",
|
|
1953
|
+
"Write a concise execution plan for the user request.",
|
|
1954
|
+
"Keep it brief and actionable.",
|
|
1955
|
+
].join(" ");
|
|
1956
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(plannerModel, plannerPrompt, userInputText);
|
|
1957
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Workflow plan:\n${workflowState.plan}`);
|
|
1958
|
+
if (workflowState.plan) {
|
|
1959
|
+
yield { kind: "reasoning", content: workflowState.plan };
|
|
1960
|
+
}
|
|
1881
1961
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
"
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1962
|
+
else if (nodeRole === "replanner") {
|
|
1963
|
+
const replannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1964
|
+
const replannerPrompt = node.prompt ?? [
|
|
1965
|
+
"You are a LangGraph workflow replanner.",
|
|
1966
|
+
"Refine the execution plan based on the current result and review feedback.",
|
|
1967
|
+
"Return an updated concise plan only.",
|
|
1968
|
+
].join(" ");
|
|
1969
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(replannerModel, replannerPrompt, [
|
|
1970
|
+
`User request:\n${userInputText}`,
|
|
1971
|
+
...(workflowState.plan ? ["", `Current plan:\n${workflowState.plan}`] : []),
|
|
1972
|
+
...(workflowState.review ? ["", `Review feedback:\n${workflowState.review}`] : []),
|
|
1973
|
+
...(activeResult ? ["", `Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1974
|
+
].join("\n"));
|
|
1975
|
+
workflowState.replans += 1;
|
|
1976
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Updated workflow plan:\n${workflowState.plan}`);
|
|
1977
|
+
if (workflowState.plan) {
|
|
1978
|
+
yield { kind: "reasoning", content: workflowState.plan };
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
else if (nodeRole === "reviewer" && activeResult) {
|
|
1982
|
+
const reviewerModel = getBindingRuntimeModel(binding, "review") ?? baseParams.model;
|
|
1983
|
+
const reviewerPrompt = node.prompt ?? [
|
|
1984
|
+
"You are a LangGraph workflow reviewer.",
|
|
1985
|
+
"Review the executor result and state whether it appears sufficient.",
|
|
1986
|
+
"Call out missing verification or obvious risks briefly.",
|
|
1987
|
+
].join(" ");
|
|
1988
|
+
workflowState.review = await this.invokeWorkflowNodeModel(reviewerModel, reviewerPrompt, [
|
|
1989
|
+
`User request:\n${userInputText}`,
|
|
1990
|
+
"",
|
|
1991
|
+
`Executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1992
|
+
].join("\n"));
|
|
1993
|
+
if (workflowState.review) {
|
|
1994
|
+
yield { kind: "reasoning", content: workflowState.review };
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
else if ((nodeRole === "finalizer" || nodeRole === "final") && activeResult) {
|
|
1998
|
+
const finalModel = getBindingRuntimeModel(binding, "final") ?? baseParams.model;
|
|
1999
|
+
const finalPrompt = node.prompt ?? [
|
|
2000
|
+
"You are a LangGraph workflow finalizer.",
|
|
2001
|
+
"Rewrite the current result into a concise user-facing answer.",
|
|
2002
|
+
"Preserve facts and caveats. Do not invent work that was not completed.",
|
|
2003
|
+
].join(" ");
|
|
2004
|
+
const finalized = await this.invokeWorkflowNodeModel(finalModel, finalPrompt, [
|
|
2005
|
+
`User request:\n${userInputText}`,
|
|
2006
|
+
...(workflowState.plan ? ["", `Plan:\n${workflowState.plan}`] : []),
|
|
2007
|
+
...(workflowState.review ? ["", `Review:\n${workflowState.review}`] : []),
|
|
2008
|
+
"",
|
|
2009
|
+
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
2010
|
+
].join("\n"));
|
|
2011
|
+
activeResult = {
|
|
2012
|
+
...activeResult,
|
|
2013
|
+
output: finalized,
|
|
2014
|
+
messages: [{ role: "assistant", content: finalized }],
|
|
2015
|
+
workflow: {
|
|
2016
|
+
...workflowState,
|
|
2017
|
+
},
|
|
2018
|
+
};
|
|
2019
|
+
const nextOutput = computeIncrementalOutput(emittedOutput, finalized);
|
|
2020
|
+
emittedOutput = nextOutput.accumulated;
|
|
2021
|
+
if (nextOutput.delta) {
|
|
2022
|
+
yield { kind: "content", content: nextOutput.delta };
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
else {
|
|
2026
|
+
const llmModel = getBindingRuntimeModel(binding, "execution") ?? baseParams.model;
|
|
2027
|
+
const llmPrompt = node.prompt ?? "Produce the next response for this workflow node.";
|
|
2028
|
+
const llmOutput = await this.invokeWorkflowNodeModel(llmModel, llmPrompt, userInputText);
|
|
2029
|
+
activeResult = {
|
|
2030
|
+
output: llmOutput,
|
|
2031
|
+
messages: [{ role: "assistant", content: llmOutput }],
|
|
2032
|
+
};
|
|
2033
|
+
const nextOutput = computeIncrementalOutput(emittedOutput, llmOutput);
|
|
2034
|
+
emittedOutput = nextOutput.accumulated;
|
|
2035
|
+
if (nextOutput.delta) {
|
|
2036
|
+
yield { kind: "content", content: nextOutput.delta };
|
|
2037
|
+
}
|
|
1900
2038
|
}
|
|
1901
2039
|
}
|
|
1902
|
-
else if (node.kind === "
|
|
1903
|
-
if (
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
const
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
if (outputKey && seenTerminalOutputs.has(outputKey)) {
|
|
1929
|
-
continue;
|
|
2040
|
+
else if (node.kind === "agent") {
|
|
2041
|
+
if (node.agent) {
|
|
2042
|
+
activeResult = await this.invokeLangGraphAgentNode(binding, node.agent, userInputText, workflowState, activeResult, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
2043
|
+
}
|
|
2044
|
+
else {
|
|
2045
|
+
if (typeof executorRunnable.streamEvents === "function") {
|
|
2046
|
+
const executorEvents = await executorRunnable.streamEvents(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, version: "v2", ...(options.context ? { context: options.context } : {}) });
|
|
2047
|
+
let executorOutput = "";
|
|
2048
|
+
const seenTerminalOutputs = new Set();
|
|
2049
|
+
for await (const event of executorEvents) {
|
|
2050
|
+
const reasoning = extractReasoningStreamOutput(event);
|
|
2051
|
+
if (reasoning) {
|
|
2052
|
+
yield { kind: "reasoning", content: reasoning };
|
|
2053
|
+
}
|
|
2054
|
+
const toolResult = extractToolResult(event);
|
|
2055
|
+
if (toolResult) {
|
|
2056
|
+
yield {
|
|
2057
|
+
kind: "tool-result",
|
|
2058
|
+
toolName: toolResult.toolName,
|
|
2059
|
+
output: toolResult.output,
|
|
2060
|
+
isError: toolResult.isError,
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
const visibleStreamOutput = extractVisibleStreamOutput(event);
|
|
2064
|
+
if (visibleStreamOutput) {
|
|
2065
|
+
executorOutput = computeIncrementalOutput(executorOutput, visibleStreamOutput).accumulated;
|
|
1930
2066
|
}
|
|
1931
|
-
|
|
1932
|
-
|
|
2067
|
+
const terminalOutput = extractTerminalStreamOutput(event);
|
|
2068
|
+
if (terminalOutput) {
|
|
2069
|
+
const outputKey = normalizeTerminalOutputKey(terminalOutput);
|
|
2070
|
+
if (outputKey && seenTerminalOutputs.has(outputKey)) {
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
if (outputKey) {
|
|
2074
|
+
seenTerminalOutputs.add(outputKey);
|
|
2075
|
+
}
|
|
2076
|
+
executorOutput = computeIncrementalOutput(executorOutput, sanitizeVisibleText(terminalOutput)).accumulated;
|
|
1933
2077
|
}
|
|
1934
|
-
executorOutput = computeIncrementalOutput(executorOutput, sanitizeVisibleText(terminalOutput)).accumulated;
|
|
1935
2078
|
}
|
|
2079
|
+
activeResult = executorOutput
|
|
2080
|
+
? {
|
|
2081
|
+
output: executorOutput,
|
|
2082
|
+
messages: [{ role: "assistant", content: executorOutput }],
|
|
2083
|
+
}
|
|
2084
|
+
: await executorRunnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
2085
|
+
}
|
|
2086
|
+
else {
|
|
2087
|
+
activeResult = await executorRunnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
1936
2088
|
}
|
|
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
2089
|
}
|
|
1947
2090
|
for (const toolResult of this.extractExecutedToolResults(activeResult)) {
|
|
1948
2091
|
yield {
|
|
@@ -1953,11 +2096,11 @@ export class AgentRuntimeAdapter {
|
|
|
1953
2096
|
};
|
|
1954
2097
|
}
|
|
1955
2098
|
}
|
|
1956
|
-
else if (node.kind === "
|
|
1957
|
-
if (!node.
|
|
1958
|
-
throw new Error(`LangGraph agent ${binding.agent.id}
|
|
2099
|
+
else if (node.kind === "tool") {
|
|
2100
|
+
if (!node.tool) {
|
|
2101
|
+
throw new Error(`LangGraph agent ${binding.agent.id} tool node ${node.id} requires tool`);
|
|
1959
2102
|
}
|
|
1960
|
-
activeResult = await this.
|
|
2103
|
+
activeResult = await this.invokeLangGraphToolNode(binding, node.tool, userInputText, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) }, node.args);
|
|
1961
2104
|
for (const toolResult of this.extractExecutedToolResults(activeResult)) {
|
|
1962
2105
|
yield {
|
|
1963
2106
|
kind: "tool-result",
|
|
@@ -1967,21 +2110,8 @@ export class AgentRuntimeAdapter {
|
|
|
1967
2110
|
};
|
|
1968
2111
|
}
|
|
1969
2112
|
}
|
|
1970
|
-
else if (node.kind === "
|
|
1971
|
-
|
|
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
|
-
}
|
|
2113
|
+
else if (node.kind === "condition") {
|
|
2114
|
+
// Condition nodes are routing-only. Edge conditions decide the next node.
|
|
1985
2115
|
}
|
|
1986
2116
|
else if (node.kind === "approval") {
|
|
1987
2117
|
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
@@ -2008,39 +2138,11 @@ export class AgentRuntimeAdapter {
|
|
|
2008
2138
|
yield { kind: "interrupt", content: JSON.stringify([interruptPayload]) };
|
|
2009
2139
|
return;
|
|
2010
2140
|
}
|
|
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
2141
|
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
2040
2142
|
currentNodeId = nextNodes[0];
|
|
2041
2143
|
}
|
|
2042
2144
|
if (!activeResult) {
|
|
2043
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an
|
|
2145
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an agent or tool node`);
|
|
2044
2146
|
}
|
|
2045
2147
|
if (sessionIdentity) {
|
|
2046
2148
|
await this.clearLangGraphSession(binding, sessionIdentity);
|