LibreChat/packages/api/src/agents/edges.ts
Danny Avila 791dab8f20
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
🫱🏼🫲🏽 refactor: Improve Agent Handoffs (#11172)
* fix: Tool Resources Dropped between Agent Handoffs

* fix: agent deletion process to remove handoff edges

- Added logic to the `deleteAgent` function to remove references to the deleted agent from other agents' handoff edges.
- Implemented error handling to log any issues encountered during the edge removal process.
- Introduced a new test case to verify that handoff edges are correctly removed when an agent is deleted, ensuring data integrity across agent relationships.

* fix: Improve agent loading process by handling orphaned references

- Added logic to track and log agents that fail to load during initialization, preventing errors from interrupting the process.
- Introduced a Set to store skipped agent IDs and updated edge filtering to exclude these orphaned references, enhancing data integrity in agent relationships.

* chore: Update @librechat/agents to version 3.0.62

* feat: Enhance agent initialization with edge collection and filtering

- Introduced new functions for edge collection and filtering orphaned edges, improving the agent loading process.
- Refactored the `initializeClient` function to utilize breadth-first search (BFS) for discovering connected agents, enabling transitive handoffs.
- Added a new module for edge-related utilities, including deduplication and participant extraction, to streamline edge management.
- Updated the agent configuration handling to ensure proper edge processing and integrity during initialization.

* refactor: primary agent ID selection for multi-agent conversations

- Added a new function `findPrimaryAgentId` to determine the primary agent ID from a set of agent IDs based on suffix rules.
- Updated `createMultiAgentMapper` to filter messages by primary agent for parallel agents and handle handoffs appropriately.
- Enhanced message processing logic to ensure correct inclusion of agent content based on group and agent ID presence.
- Improved documentation to clarify the distinctions between parallel execution and handoff scenarios.

* feat: Implement primary agent ID selection for multi-agent content filtering

* chore: Update @librechat/agents to version 3.0.63 in package.json and package-lock.json

* chore: Update @librechat/agents to version 3.0.64 in package.json and package-lock.json

* chore: Update @librechat/agents to version 3.0.65 in package.json and package-lock.json

* feat: Add optional agent name to run creation for improved identification

* chore: Update @librechat/agents to version 3.0.66 in package.json and package-lock.json

* test: Add unit tests for edge utilities including key generation, participant extraction, and orphaned edge filtering

- Implemented tests for `getEdgeKey`, `getEdgeParticipants`, `filterOrphanedEdges`, and `createEdgeCollector` functions.
- Ensured comprehensive coverage for various edge cases, including handling of arrays and default values.
- Verified correct behavior of edge filtering based on skipped agents and deduplication of edges.
2026-01-01 16:02:51 -05:00

90 lines
2.7 KiB
TypeScript

import type { GraphEdge } from 'librechat-data-provider';
/**
* Creates a stable key for edge deduplication.
* Handles both single and array-based from/to values.
*/
export function getEdgeKey(edge: GraphEdge): string {
const from = Array.isArray(edge.from) ? [...edge.from].sort().join('|') : edge.from;
const to = Array.isArray(edge.to) ? [...edge.to].sort().join('|') : edge.to;
const type = edge.edgeType ?? 'handoff';
return `${from}=>${to}::${type}`;
}
/**
* Extracts all agent IDs referenced in an edge (both from and to).
*/
export function getEdgeParticipants(edge: GraphEdge): string[] {
const participants: string[] = [];
if (Array.isArray(edge.from)) {
participants.push(...edge.from);
} else if (typeof edge.from === 'string') {
participants.push(edge.from);
}
if (Array.isArray(edge.to)) {
participants.push(...edge.to);
} else if (typeof edge.to === 'string') {
participants.push(edge.to);
}
return participants;
}
/**
* Filters out edges that reference non-existent (orphaned) agents.
* Only filters based on the 'to' field since those are the handoff targets.
*/
export function filterOrphanedEdges(edges: GraphEdge[], skippedAgentIds: Set<string>): GraphEdge[] {
if (!edges || skippedAgentIds.size === 0) {
return edges;
}
return edges.filter((edge) => {
const toIds = Array.isArray(edge.to) ? edge.to : [edge.to];
return !toIds.some((id) => typeof id === 'string' && skippedAgentIds.has(id));
});
}
/**
* Result of discovering and aggregating edges from connected agents.
*/
export interface EdgeDiscoveryResult {
/** Deduplicated edges from all discovered agents */
edges: GraphEdge[];
/** Agent IDs that were not found (orphaned references) */
skippedAgentIds: Set<string>;
}
/**
* Collects and deduplicates edges, tracking new agents to process.
* Used for BFS discovery of connected agents.
*/
export function createEdgeCollector(
checkAgentInit: (agentId: string) => boolean,
skippedAgentIds: Set<string>,
): {
edgeMap: Map<string, GraphEdge>;
agentsToProcess: Set<string>;
collectEdges: (edgeList: GraphEdge[] | undefined) => void;
} {
const edgeMap = new Map<string, GraphEdge>();
const agentsToProcess = new Set<string>();
const collectEdges = (edgeList: GraphEdge[] | undefined): void => {
if (!edgeList || edgeList.length === 0) {
return;
}
for (const edge of edgeList) {
const key = getEdgeKey(edge);
if (!edgeMap.has(key)) {
edgeMap.set(key, edge);
}
const participants = getEdgeParticipants(edge);
for (const id of participants) {
if (!checkAgentInit(id) && !skippedAgentIds.has(id)) {
agentsToProcess.add(id);
}
}
}
};
return { edgeMap, agentsToProcess, collectEdges };
}