UNPKG

langchain

Version:
205 lines (204 loc) 7.81 kB
require("../../_virtual/_rolldown/runtime.cjs"); const require_tool_call = require("./tool-call.cjs"); let _langchain_langgraph = require("@langchain/langgraph"); //#region src/agents/transformers/subagent.ts function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } /** Stable string key for a namespace. */ function nsKey(ns) { return ns.join("\0"); } /** Tests whether `ns` starts with every segment in `prefix`. */ function hasPrefix(ns, prefix) { if (prefix.length > ns.length) return false; for (let i = 0; i < prefix.length; i += 1) if (ns[i] !== prefix[i]) return false; return true; } /** * Creates a native transformer that surfaces nested named agents on * `run.subagents`. * * It watches `tasks` events to record each namespace's `lc_agent_name` (set by * `createAgent({ name })`) and the triggering tool call, then — for any nested * run one level below {@link scope} that carries an `lc_agent_name` — emits a * typed {@link SubagentRunStream} handle. * * Each handle is backed by its own per-subagent transformer instances * ({@link createMessagesTransformer}, {@link createToolCallTransformer}, and a * nested {@link createSubagentTransformer}) scoped to the subagent's namespace. * Every event in the subtree is fed straight into those transformers, which * self-filter by namespace; the subagent's final `output` is resolved from its * last `values` snapshot when its `lifecycle` completes. * * Marked `__native: true` — the `subagents` projection lands directly on the * `GraphRunStream` instance as `run.subagents`. * * @param scope - Namespace prefix this transformer is scoped to. The root agent * uses `[]`; nested handles use their subagent's namespace, so grandchild * subagents are discovered recursively. */ function createSubagentTransformer(scope = []) { return () => { const subagentsLog = _langchain_langgraph.StreamChannel.local(); /** `lc_agent_name` observed per namespace (first task event wins). */ const lcByNs = /* @__PURE__ */ new Map(); /** Triggering task id -> originating LLM `tool_call_id`. */ const pendingToolCalls = /* @__PURE__ */ new Map(); /** * Namespace key -> the `tool_call_id` of the most recent tool to start * executing there. A tool that invokes a subagent emits its `tool-started` * at the tools-node namespace (`tools:<task_id>`) where the subagent then * roots, so this is the tool call that caused the subagent. */ const activeToolCallByNs = /* @__PURE__ */ new Map(); const handles = /* @__PURE__ */ new Map(); const depth = scope.length; function recordIdentity(ns, data) { const key = nsKey(ns); if (lcByNs.has(key)) return; const lc = (isRecord(data) && isRecord(data.metadata) ? data.metadata : void 0)?.lc_agent_name; lcByNs.set(key, typeof lc === "string" ? lc : void 0); } function recordPendingToolCalls(data) { if (!isRecord(data)) return; const taskId = data.id; if (typeof taskId !== "string") return; const input = data.input; let toolCallId; if (isRecord(input) && isRecord(input.tool_call)) { const candidate = input.tool_call.id; if (typeof candidate === "string") toolCallId = candidate; } else if (Array.isArray(input)) { for (const toolCall of input) if (isRecord(toolCall) && typeof toolCall.id === "string") { toolCallId = toolCall.id; break; } } if (toolCallId != null) pendingToolCalls.set(taskId, toolCallId); } /** * Derive the `toolCall` cause for a named-subagent namespace. * * Primary signal: the tool whose `tool-started` event fired at the * subagent's own namespace (the tools node it roots under). Fallback: the * namespace segment's task id (`node:<task_id>`) joined to a tool call * harvested from a `tool_call_with_context`-shaped task input, so the * derivation stays correct if that shape reaches the stream in the future. */ function deriveCause(ns) { const active = activeToolCallByNs.get(nsKey(ns)); if (typeof active === "string" && active.length > 0) return { type: "toolCall", tool_call_id: active }; const segment = ns[ns.length - 1]; const colon = segment.indexOf(":"); if (colon === -1) return void 0; const triggerCallId = segment.slice(colon + 1); if (triggerCallId.length === 0) return void 0; const toolCallId = pendingToolCalls.get(triggerCallId); if (typeof toolCallId !== "string" || toolCallId.length === 0) return; return { type: "toolCall", tool_call_id: toolCallId }; } function maybeStartSubagent(ns) { if (ns.length !== depth + 1 || !hasPrefix(ns, scope)) return; const key = nsKey(ns); if (handles.has(key)) return; const lc = lcByNs.get(key); if (typeof lc !== "string" || lc.length === 0) return; const messages = (0, _langchain_langgraph.createMessagesTransformer)(ns); const messagesProjection = messages.init(); const toolCall = require_tool_call.createToolCallTransformer(ns)(); const toolCallProjection = toolCall.init(); const nested = createSubagentTransformer(ns)(); const nestedProjection = nested.init(); let resolveOutput; let rejectOutput; const output = new Promise((resolve, reject) => { resolveOutput = resolve; rejectOutput = reject; }); handles.set(key, { key, path: ns, name: lc, messages, toolCall, nested, resolveOutput, rejectOutput, latestValues: void 0, done: false }); subagentsLog.push({ name: lc, cause: deriveCause(ns), output, messages: messagesProjection.messages, toolCalls: toolCallProjection.toolCalls, subagents: nestedProjection.subagents }); } function finishHandle(handle, outcome) { if (handle.done) return; handle.done = true; if (outcome.type === "resolve") handle.resolveOutput(handle.latestValues); else handle.rejectOutput(outcome.error); handle.messages.finalize?.(); handle.toolCall.finalize?.(); handle.nested.finalize?.(); } return { __native: true, init: () => ({ subagents: subagentsLog }), process(event) { const ns = event.params.namespace; const data = event.params.data; const isTaskResult = event.method === "tasks" && isRecord(data) && "result" in data; if (event.method === "tools" && isRecord(data) && data.event === "tool-started" && typeof data.tool_call_id === "string" && data.tool_call_id.length > 0) activeToolCallByNs.set(nsKey(ns), data.tool_call_id); if (event.method === "tasks" && !isTaskResult) { recordIdentity(ns, data); recordPendingToolCalls(data); maybeStartSubagent(ns); } for (const handle of handles.values()) { if (handle.done) continue; if (!hasPrefix(ns, handle.path)) continue; handle.messages.process(event); handle.toolCall.process(event); handle.nested.process(event); if (nsKey(ns) === handle.key) { if (event.method === "values" && isRecord(data)) handle.latestValues = data; else if (event.method === "lifecycle" && isRecord(data)) { const status = data.event; if (status === "completed" || status === "interrupted") finishHandle(handle, { type: "resolve" }); else if (status === "failed") finishHandle(handle, { type: "reject", error: /* @__PURE__ */ new Error(`Subagent ${handle.name} failed`) }); } } } return true; }, finalize() { for (const handle of handles.values()) finishHandle(handle, { type: "resolve" }); subagentsLog.close(); }, fail(err) { for (const handle of handles.values()) finishHandle(handle, { type: "reject", error: err }); subagentsLog.fail(err); } }; }; } //#endregion exports.createSubagentTransformer = createSubagentTransformer; //# sourceMappingURL=subagent.cjs.map